diff --git a/seinfo b/seinfo index ef5fea7..c682d99 100755 --- a/seinfo +++ b/seinfo @@ -101,88 +101,84 @@ try: if args.boolquery or args.all: q = setools.BoolQuery(p) if isinstance(args.boolquery, str): - q.set_name(args.boolquery) + q.name = args.boolquery components.append(("Booleans", q, lambda x: x.statement())) if args.mlscatsquery or args.all: q = setools.CategoryQuery(p) if isinstance(args.mlscatsquery, str): - q.set_name(args.mlscatsquery) + q.name = args.mlscatsquery components.append(("Categories", q, lambda x: x.statement())) if args.classquery or args.all: q = setools.ObjClassQuery(p) if isinstance(args.classquery, str): - q.set_name(args.classquery) + q.name = args.classquery components.append(("Classes", q, lambda x: x.statement())) if args.commonquery or args.all: q = setools.CommonQuery(p) if isinstance(args.commonquery, str): - q.set_name(args.commonquery) + q.name = args.commonquery components.append(("Commons", q, lambda x: x.statement())) if args.constraintquery or args.all: q = setools.ConstraintQuery(p, ruletype=["constrain", "mlsconstrain"]) if isinstance(args.constraintquery, str): - # pylint: disable=no-member - q.set_tclass(args.constraintquery) + q.tclass = [args.constraintquery] components.append(("Constraints", q, lambda x: x.statement())) if args.fsusequery or args.all: q = setools.FSUseQuery(p) if isinstance(args.fsusequery, str): - # pylint: disable=no-member - q.set_fs(args.fsusequery) + q.fs = args.fsusequery components.append(("Fs_use", q, lambda x: x.statement())) if args.genfsconquery or args.all: q = setools.GenfsconQuery(p) if isinstance(args.genfsconquery, str): - # pylint: disable=no-member - q.set_fs(args.genfsconquery) + q.fs = args.genfsconquery components.append(("Genfscon", q, lambda x: x.statement())) if args.initialsidquery or args.all: q = setools.InitialSIDQuery(p) if isinstance(args.initialsidquery, str): - q.set_name(args.initialsidquery) + q.name = args.initialsidquery components.append(("Initial SIDs", q, lambda x: x.statement())) if args.netifconquery or args.all: q = setools.NetifconQuery(p) if isinstance(args.netifconquery, str): - q.set_name(args.netifconquery) + q.name = args.netifconquery components.append(("Netifcon", q, lambda x: x.statement())) if args.nodeconquery or args.all: q = setools.NodeconQuery(p) if isinstance(args.nodeconquery, str): - # pylint: disable=no-member - q.set_network(args.nodeconquery) + q.network = args.nodeconquery components.append(("Nodecon", q, lambda x: x.statement())) if args.permissivequery or args.all: q = setools.TypeQuery(p, permissive=True, match_permissive=True) if isinstance(args.permissivequery, str): - q.set_name(args.permissivequery) + q.name = args.permissivequery components.append(("Permissive Types", q, lambda x: x.statement())) if args.polcapquery or args.all: q = setools.PolCapQuery(p) if isinstance(args.polcapquery, str): - q.set_name(args.polcapquery) + q.name = args.polcapquery components.append(("Polcap", q, lambda x: x.statement())) @@ -195,11 +191,9 @@ try: parser.error("Enter a port number or range, e.g. 22 or 6000-6020") if len(ports) == 2: - # pylint: disable=no-member - q.set_ports((ports[0], ports[1])) + q.ports = ports elif len(ports) == 1: - # pylint: disable=no-member - q.set_ports((ports[0], ports[0])) + q.ports = (ports[0], ports[0]) else: parser.error("Enter a port number or range, e.g. 22 or 6000-6020") @@ -208,43 +202,42 @@ try: if args.rolequery or args.all: q = setools.RoleQuery(p) if isinstance(args.rolequery, str): - q.set_name(args.rolequery) + q.name = args.rolequery components.append(("Roles", q, lambda x: x.statement())) if args.mlssensquery or args.all: q = setools.SensitivityQuery(p) if isinstance(args.mlssensquery, str): - q.set_name(args.mlssensquery) + q.name = args.mlssensquery components.append(("Sensitivities", q, lambda x: x.statement())) if args.typequery or args.all: q = setools.TypeQuery(p) if isinstance(args.typequery, str): - q.set_name(args.typequery) + q.name = args.typequery components.append(("Types", q, lambda x: x.statement())) if args.typeattrquery or args.all: q = setools.TypeAttributeQuery(p) if isinstance(args.typeattrquery, str): - q.set_name(args.typeattrquery) + q.name = args.typeattrquery components.append(("Type Attributes", q, expand_attr)) if args.userquery or args.all: q = setools.UserQuery(p) if isinstance(args.userquery, str): - q.set_name(args.userquery) + q.name = args.userquery components.append(("Users", q, lambda x: x.statement())) if args.validatetransquery or args.all: q = setools.ConstraintQuery(p, ruletype=["validatetrans", "mlsvalidatetrans"]) if isinstance(args.validatetransquery, str): - # pylint: disable=no-member - q.set_tclass(args.validatetransquery) + q.tclass = [args.validatetransquery] components.append(("Validatetrans", q, lambda x: x.statement())) diff --git a/seinfoflow b/seinfoflow index 14b6ee0..6cea534 100755 --- a/seinfoflow +++ b/seinfoflow @@ -79,7 +79,7 @@ else: try: p = setools.SELinuxPolicy(args.policy) m = setools.PermissionMap(args.map) - g = setools.InfoFlowAnalysis(p, m, minweight=args.min_weight, exclude=args.exclude) + g = setools.InfoFlowAnalysis(p, m, min_weight=args.min_weight, exclude=args.exclude) if args.shortest_path or args.all_paths: if args.shortest_path: diff --git a/sesearch b/sesearch index 35aac06..e861db6 100755 --- a/sesearch +++ b/sesearch @@ -135,18 +135,18 @@ try: # with an empty string in it (split on empty string) if args.tclass: if args.tclass_regex: - q.set_tclass(args.tclass) + q.tclass = args.tclass else: - q.set_tclass(args.tclass.split(",")) + q.tclass = args.tclass.split(",") if args.perms: - q.set_perms(args.perms.split(",")) + q.perms = args.perms.split(",") if args.boolean: if args.boolean_regex: - q.set_boolean(args.boolean) + q.boolean = args.boolean else: - q.set_boolean(args.boolean.split(",")) + q.boolean = args.boolean.split(",") for r in sorted(q.results()): print(r) @@ -168,9 +168,9 @@ try: # with an empty string in it (split on empty string) if args.tclass: if args.tclass_regex: - q.set_tclass(args.tclass) + q.tclass = args.tclass else: - q.set_tclass(args.tclass.split(",")) + q.tclass = args.tclass.split(",") for r in sorted(q.results()): print(r) @@ -189,9 +189,9 @@ try: # with an empty string in it (split on empty string) if args.tclass: if args.tclass_regex: - q.set_tclass(args.tclass) + q.tclass = args.tclass else: - q.set_tclass(args.tclass.split(",")) + q.tclass = args.tclass.split(",") for r in sorted(q.results()): print(r) diff --git a/setools/boolquery.py b/setools/boolquery.py index 9bf4971..b70b7d5 100644 --- a/setools/boolquery.py +++ b/setools/boolquery.py @@ -19,61 +19,48 @@ import logging from . import compquery +from .descriptors import CriteriaDescriptor class BoolQuery(compquery.ComponentQuery): - """Query SELinux policy Booleans.""" + """Query SELinux policy Booleans. - def __init__(self, policy, - name=None, name_regex=False, - default=False, match_default=False): - """ - Parameter: - policy The policy to query. - name The Boolean name to match. - name_regex If true, regular expression matching - will be used on the Boolean name. - default The default state to match. - match_default If true, the default state will be matched. - """ - self.log = logging.getLogger(self.__class__.__name__) + Parameter: + policy The policy to query. - self.policy = policy - self.set_name(name, regex=name_regex) - self.set_default(match_default, default=default) + Keyword Parameters/Class attributes: + name The Boolean name to match. + name_regex If true, regular expression matching + will be used on the Boolean name. + default The default state to match. If this + is None, the default state not be matched. + """ + + _default = None + + @property + def default(self): + return self._default + + @default.setter + def default(self, value): + if value is None: + self._default = None + else: + self._default = bool(value) def results(self): """Generator which yields all Booleans matching the criteria.""" self.log.info("Generating results from {0.policy}".format(self)) - self.log.debug("Name: {0.name_cmp!r}, regex: {0.name_regex}".format(self)) - self.log.debug("Default: {0.match_default}, state: {0.default}".format(self)) + self.log.debug("Name: {0.name!r}, regex: {0.name_regex}".format(self)) + self.log.debug("Default: {0.default}".format(self)) for boolean in self.policy.bools(): - if self.name and not self._match_name(boolean): + if not self._match_name(boolean): continue - if self.match_default and boolean.state != self.default: + if self.default is not None and boolean.state != self.default: continue yield boolean - - def set_default(self, match, **opts): - """ - Set if the default Boolean state should be matched. - - Parameter: - match If true, the default state will be matched. - default The default state to match. - - Exceptions: - NameError Invalid keyword option. - """ - - self.match_default = bool(match) - - for k in list(opts.keys()): - if k == "default": - self.default = bool(opts[k]) - else: - raise NameError("Invalid default option: {0}".format(k)) diff --git a/setools/categoryquery.py b/setools/categoryquery.py index 27f8595..d4d7c4c 100644 --- a/setools/categoryquery.py +++ b/setools/categoryquery.py @@ -24,37 +24,32 @@ from . import mixins class CategoryQuery(mixins.MatchAlias, compquery.ComponentQuery): - """Query MLS Categories""" + """ + Query MLS Categories - def __init__(self, policy, - name=None, name_regex=False, - alias=None, alias_regex=False): - """ - Parameters: - name The name of the category to match. - name_regex If true, regular expression matching will - be used for matching the name. - alias The alias name to match. - alias_regex If true, regular expression matching - will be used on the alias names. - """ - self.log = logging.getLogger(self.__class__.__name__) + Parameter: + policy The policy to query. - self.policy = policy - self.set_name(name, regex=name_regex) - self.set_alias(alias, regex=alias_regex) + Keyword Parameters/Class attributes: + name The name of the category to match. + name_regex If true, regular expression matching will + be used for matching the name. + alias The alias name to match. + alias_regex If true, regular expression matching + will be used on the alias names. + """ def results(self): """Generator which yields all matching categories.""" self.log.info("Generating results from {0.policy}".format(self)) - self.log.debug("Name: {0.name_cmp!r}, regex: {0.name_regex}".format(self)) - self.log.debug("Alias: {0.alias_cmp}, regex: {0.alias_regex}".format(self)) + self.log.debug("Name: {0.name!r}, regex: {0.name_regex}".format(self)) + self.log.debug("Alias: {0.alias}, regex: {0.alias_regex}".format(self)) for cat in self.policy.categories(): - if self.name and not self._match_name(cat): + if not self._match_name(cat): continue - if self.alias and not self._match_alias(cat.aliases()): + if not self._match_alias(cat): continue yield cat diff --git a/setools/commonquery.py b/setools/commonquery.py index 543914f..e105ccb 100644 --- a/setools/commonquery.py +++ b/setools/commonquery.py @@ -19,84 +19,42 @@ import logging import re -from . import compquery +from . import compquery, mixins -class CommonQuery(compquery.ComponentQuery): +class CommonQuery(mixins.MatchPermission, compquery.ComponentQuery): - """Query common permission sets.""" + """ + Query common permission sets. - def __init__(self, policy, - name=None, name_regex=False, - perms=None, perms_equal=False, perms_regex=False): - """ - Parameters: - name The name of the common to match. - name_regex If true, regular expression matching will - be used for matching the name. - perms The permissions to match. - perms_equal If true, only commons with permission sets - that are equal to the criteria will - match. Otherwise, any intersection - will match. - perms_regex If true, regular expression matching will be used - on the permission names instead of set logic. - """ - self.log = logging.getLogger(self.__class__.__name__) + Parameter: + policy The policy to query. - self.policy = policy - self.set_name(name, regex=name_regex) - self.set_perms(perms, regex=perms_regex, equal=perms_equal) + Keyword Parameters/Class attributes: + name The name of the common to match. + name_regex If true, regular expression matching will + be used for matching the name. + perms The permissions to match. + perms_equal If true, only commons with permission sets + that are equal to the criteria will + match. Otherwise, any intersection + will match. + perms_regex If true, regular expression matching will be used + on the permission names instead of set logic. + """ def results(self): """Generator which yields all matching commons.""" self.log.info("Generating results from {0.policy}".format(self)) - self.log.debug("Name: {0.name_cmp!r}, regex: {0.name_regex}".format(self)) - self.log.debug("Perms: {0.perms_cmp!r}, regex: {0.perms_regex}, eq: {0.perms_equal}". + self.log.debug("Name: {0.name!r}, regex: {0.name_regex}".format(self)) + self.log.debug("Perms: {0.perms!r}, regex: {0.perms_regex}, eq: {0.perms_equal}". format(self)) for com in self.policy.commons(): - if self.name and not self._match_name(com): + if not self._match_name(com): continue - if self.perms and not self._match_regex_or_set( - com.perms, - self.perms_cmp, - self.perms_equal, - self.perms_regex): + if not self._match_perms(com): continue yield com - - def set_perms(self, perms, **opts): - """ - Set the criteria for the common's permissions. - - Parameter: - perms Name to match the common's permissions. - - Keyword Options: - regex If true, regular expression matching will be used. - equal If true, the permisison set of the common - must equal the permissions criteria to - match. If false, any intersection in the - critera will cause a match. - - Exceptions: - NameError Invalid keyword option. - """ - - self.perms = perms - - for k in list(opts.keys()): - if k == "regex": - self.perms_regex = opts[k] - elif k == "equal": - self.perms_equal = opts[k] - else: - raise NameError("Invalid permissions option: {0}".format(k)) - - if self.perms_regex: - self.perms_cmp = re.compile(self.perms) - else: - self.perms_cmp = self.perms diff --git a/setools/compquery.py b/setools/compquery.py index 89be9ac..3d8851a 100644 --- a/setools/compquery.py +++ b/setools/compquery.py @@ -20,37 +20,20 @@ import re from . import query +from .descriptors import CriteriaDescriptor class ComponentQuery(query.PolicyQuery): - """Abstract base class for SETools component queries.""" + """Base class for SETools component queries.""" + + name = CriteriaDescriptor("name_regex") + name_regex = False def _match_name(self, obj): """Match the object to the name criteria.""" - return self._match_regex(obj, self.name_cmp, self.name_regex) + if not self.name: + # if there is no criteria, everything matches. + return True - def set_name(self, name, **opts): - """ - Set the criteria for matching the component's name. - - Parameter: - name Name to match the component's name. - regex If true, regular expression matching will be used. - - Exceptions: - NameError Invalid keyword option. - """ - - self.name = name - - for k in list(opts.keys()): - if k == "regex": - self.name_regex = opts[k] - else: - raise NameError("Invalid name option: {0}".format(k)) - - if self.name_regex: - self.name_cmp = re.compile(self.name) - else: - self.name_cmp = self.name + return self._match_regex(obj, self.name, self.name_regex) diff --git a/setools/constraintquery.py b/setools/constraintquery.py index bac43e1..82a6fc2 100644 --- a/setools/constraintquery.py +++ b/setools/constraintquery.py @@ -19,62 +19,58 @@ import logging import re -from . import mixins -from .query import PolicyQuery +from . import mixins, query +from .descriptors import CriteriaDescriptor, CriteriaSetDescriptor, RuletypeDescriptor from .policyrep.exception import ConstraintUseError -class ConstraintQuery(mixins.MatchObjClass, mixins.MatchPermission, PolicyQuery): +class ConstraintQuery(mixins.MatchObjClass, mixins.MatchPermission, query.PolicyQuery): - """Query constraint rules, (mls)constrain/(mls)validatetrans.""" + """ + Query constraint rules, (mls)constrain/(mls)validatetrans. - def __init__(self, policy, - ruletype=None, - tclass=None, tclass_regex=False, - perms=None, perms_equal=False, - role=None, role_regex=False, role_indirect=True, - type_=None, type_regex=False, type_indirect=True, - user=None, user_regex=False): + Parameter: + policy The policy to query. - """ - Parameter: - policy The policy to query. - ruletype The rule type(s) to match. - tclass The object class(es) to match. - tclass_regex If true, use a regular expression for - matching the rule's object class. - perms The permission(s) to match. - perms_equal If true, the permission set of the rule - must exactly match the permissions - criteria. If false, any set intersection - will match. - role The name of the role to match in the - constraint expression. - role_indirect If true, members of an attribute will be - matched rather than the attribute itself. - role_regex If true, regular expression matching will - be used on the role. - type_ The name of the type/attribute to match in the - constraint expression. - type_indirect If true, members of an attribute will be - matched rather than the attribute itself. - type_regex If true, regular expression matching will - be used on the type/attribute. - user The name of the user to match in the - constraint expression. - user_regex If true, regular expression matching will - be used on the user. - """ - self.log = logging.getLogger(self.__class__.__name__) + Keyword Parameters/Class attributes: + ruletype The list of rule type(s) to match. + tclass The object class(es) to match. + tclass_regex If true, use a regular expression for + matching the rule's object class. + perms The permission(s) to match. + perms_equal If true, the permission set of the rule + must exactly match the permissions + criteria. If false, any set intersection + will match. + perms_regex If true, regular expression matching will be used + on the permission names instead of set logic. + role The name of the role to match in the + constraint expression. + role_indirect If true, members of an attribute will be + matched rather than the attribute itself. + role_regex If true, regular expression matching will + be used on the role. + type_ The name of the type/attribute to match in the + constraint expression. + type_indirect If true, members of an attribute will be + matched rather than the attribute itself. + type_regex If true, regular expression matching will + be used on the type/attribute. + user The name of the user to match in the + constraint expression. + user_regex If true, regular expression matching will + be used on the user. + """ - self.policy = policy - - self.set_ruletype(ruletype) - self.set_tclass(tclass, regex=tclass_regex) - self.set_perms(perms, equal=perms_equal) - self.set_role(role, regex=role_regex, indirect=role_indirect) - self.set_type(type_, regex=type_regex, indirect=type_indirect) - self.set_user(user, regex=user_regex) + ruletype = RuletypeDescriptor("validate_constraint_ruletype") + user = CriteriaDescriptor("user_regex", "lookup_user") + user_regex = False + role = CriteriaDescriptor("role_regex", "lookup_role") + role_regex = False + role_indirect = True + type_ = CriteriaDescriptor("type_regex", "lookup_type_or_attr") + type_regex = False + type_indirect = True def _match_expr(self, expr, criteria, indirect, regex): """ @@ -101,143 +97,46 @@ class ConstraintQuery(mixins.MatchObjClass, mixins.MatchPermission, PolicyQuery) """Generator which yields all matching constraints rules.""" self.log.info("Generating results from {0.policy}".format(self)) self.log.debug("Ruletypes: {0.ruletype}".format(self)) - self.log.debug("Class: {0.tclass_cmp!r}, regex: {0.tclass_regex}".format(self)) - self.log.debug("Perms: {0.perms_cmp}, eq: {0.perms_equal}".format(self)) - self.log.debug("User: {0.user_cmp!r}, regex: {0.user_regex}".format(self)) - self.log.debug("Role: {0.role_cmp!r}, regex: {0.role_regex}".format(self)) - self.log.debug("Type: {0.type_cmp!r}, regex: {0.type_regex}".format(self)) + self.log.debug("Class: {0.tclass!r}, regex: {0.tclass_regex}".format(self)) + self.log.debug("Perms: {0.perms!r}, regex: {0.perms_regex}, eq: {0.perms_equal}". + format(self)) + self.log.debug("User: {0.user!r}, regex: {0.user_regex}".format(self)) + self.log.debug("Role: {0.role!r}, regex: {0.role_regex}".format(self)) + self.log.debug("Type: {0.type_!r}, regex: {0.type_regex}".format(self)) for c in self.policy.constraints(): if self.ruletype: if c.ruletype not in self.ruletype: continue - if self.tclass and not self._match_object_class(c.tclass): + if not self._match_object_class(c): continue - if self.perms: - try: - if not self._match_perms(c.perms): - continue - except ConstraintUseError: - continue + try: + if not self._match_perms(c): + continue + except ConstraintUseError: + continue if self.role and not self._match_expr( c.roles, - self.role_cmp, + self.role, self.role_indirect, self.role_regex): continue if self.type_ and not self._match_expr( c.types, - self.type_cmp, + self.type_, self.type_indirect, self.type_regex): continue if self.user and not self._match_expr( c.users, - self.user_cmp, + self.user, False, self.user_regex): continue yield c - - def set_ruletype(self, ruletype): - """ - Set the rule types for the rule query. - - Parameter: - ruletype The rule types to match. - """ - if ruletype: - self.policy.validate_constraint_ruletype(ruletype) - - self.ruletype = ruletype - - def set_role(self, role, **opts): - """ - Set the criteria for matching the constraint's role. - - Parameter: - role Name to match the constraint's role. - regex If true, regular expression matching will be used. - - Exceptions: - NameError Invalid keyword option. - """ - - self.role = role - - for k in list(opts.keys()): - if k == "regex": - self.role_regex = opts[k] - elif k == "indirect": - self.role_indirect = opts[k] - else: - raise NameError("Invalid name option: {0}".format(k)) - - if not self.role: - self.role_cmp = None - elif self.role_regex: - self.role_cmp = re.compile(self.role) - else: - self.role_cmp = self.policy.lookup_role(self.role) - - def set_type(self, type_, **opts): - """ - Set the criteria for matching the constraint's type. - - Parameter: - type_ Name to match the constraint's type. - regex If true, regular expression matching will be used. - - Exceptions: - NameError Invalid keyword option. - """ - - self.type_ = type_ - - for k in list(opts.keys()): - if k == "regex": - self.type_regex = opts[k] - elif k == "indirect": - self.type_indirect = opts[k] - else: - raise NameError("Invalid name option: {0}".format(k)) - - if not self.type_: - self.type_cmp = None - elif self.type_regex: - self.type_cmp = re.compile(self.type_) - else: - self.type_cmp = self.policy.lookup_type(type_) - - def set_user(self, user, **opts): - """ - Set the criteria for matching the constraint's user. - - Parameter: - user Name to match the constraint's user. - regex If true, regular expression matching will be used. - - Exceptions: - NameError Invalid keyword option. - """ - - self.user = user - - for k in list(opts.keys()): - if k == "regex": - self.user_regex = opts[k] - else: - raise NameError("Invalid name option: {0}".format(k)) - - if not self.user: - self.user_cmp = None - elif self.user_regex: - self.user_cmp = re.compile(self.user) - else: - self.user_cmp = self.policy.lookup_user(self.user) diff --git a/setools/contextquery.py b/setools/contextquery.py index f182185..5ce1632 100644 --- a/setools/contextquery.py +++ b/setools/contextquery.py @@ -20,190 +20,79 @@ import re from . import query +from .descriptors import CriteriaDescriptor class ContextQuery(query.PolicyQuery): - """Abstract base class for SETools in-policy labeling/context queries.""" + """ + Base class for SETools in-policy labeling/context queries. - @staticmethod - def _match_context(context, - user, user_regex, - role, role_regex, - type_, type_regex, - range_, range_subset, range_overlap, range_superset, range_proper): - """ - Match the context with optional regular expression. + Parameter: + policy The policy to query. - Parameters: - context The object to match. - user The user to match in the context. - user_regex If true, regular expression matching - will be used on the user. - role The role to match in the context. - role_regex If true, regular expression matching - will be used on the role. - type_ The type to match in the context. - type_regex If true, regular expression matching - will be used on the type. - range_ The range to match in the context. - range_subset If true, the criteria will match if it - is a subset of the context's range. - range_overlap If true, the criteria will match if it - overlaps any of the context's range. - range_superset If true, the criteria will match if it - is a superset of the context's range. - range_proper If true, use proper superset/subset - on range matching operations. - No effect if not using set operations. - """ + Keyword Parameters/Class attributes: + context The object to match. + user The user to match in the context. + user_regex If true, regular expression matching + will be used on the user. + role The role to match in the context. + role_regex If true, regular expression matching + will be used on the role. + type_ The type to match in the context. + type_regex If true, regular expression matching + will be used on the type. + range_ The range to match in the context. + range_subset If true, the criteria will match if it + is a subset of the context's range. + range_overlap If true, the criteria will match if it + overlaps any of the context's range. + range_superset If true, the criteria will match if it + is a superset of the context's range. + range_proper If true, use proper superset/subset + on range matching operations. + No effect if not using set operations. + """ - if user and not query.PolicyQuery._match_regex( + user = CriteriaDescriptor("user_regex", "lookup_user") + user_regex = False + role = CriteriaDescriptor("role_regex", "lookup_role") + role_regex = False + type_ = CriteriaDescriptor("type_regex", "lookup_type") + type_regex = False + range_ = CriteriaDescriptor(lookup_function="lookup_range") + range_overlap = False + range_subset = False + range_superset = False + range_proper = False + + def _match_context(self, context): + + if self.user and not query.PolicyQuery._match_regex( context.user, - user, - user_regex): + self.user, + self.user_regex): return False - if role and not query.PolicyQuery._match_regex( + if self.role and not query.PolicyQuery._match_regex( context.role, - role, - role_regex): + self.role, + self.role_regex): return False - if type_ and not query.PolicyQuery._match_regex( + if self.type_ and not query.PolicyQuery._match_regex( context.type_, - type_, - type_regex): + self.type_, + self.type_regex): return False - if range_ and not query.PolicyQuery._match_range( + if self.range_ and not query.PolicyQuery._match_range( context.range_, - range_, - range_subset, - range_overlap, - range_superset, - range_proper): + self.range_, + self.range_subset, + self.range_overlap, + self.range_superset, + self.range_proper): return False return True - - def set_user(self, user, **opts): - """ - Set the criteria for matching the context's user. - - Parameter: - user Name to match the context's user. - regex If true, regular expression matching will be used. - - Exceptions: - NameError Invalid keyword option. - """ - - self.user = user - - for k in list(opts.keys()): - if k == "regex": - self.user_regex = opts[k] - else: - raise NameError("Invalid name option: {0}".format(k)) - - if not self.user: - self.user_cmp = None - elif self.user_regex: - self.user_cmp = re.compile(self.user) - else: - self.user_cmp = self.policy.lookup_user(self.user) - - def set_role(self, role, **opts): - """ - Set the criteria for matching the context's role. - - Parameter: - role Name to match the context's role. - regex If true, regular expression matching will be used. - - Exceptions: - NameError Invalid keyword option. - """ - - self.role = role - - for k in list(opts.keys()): - if k == "regex": - self.role_regex = opts[k] - else: - raise NameError("Invalid name option: {0}".format(k)) - - if not self.role: - self.role_cmp = None - elif self.role_regex: - self.role_cmp = re.compile(self.role) - else: - self.role_cmp = self.policy.lookup_role(self.role) - - def set_type(self, type_, **opts): - """ - Set the criteria for matching the context's type. - - Parameter: - type_ Name to match the context's type. - regex If true, regular expression matching will be used. - - Exceptions: - NameError Invalid keyword option. - """ - - self.type_ = type_ - - for k in list(opts.keys()): - if k == "regex": - self.type_regex = opts[k] - else: - raise NameError("Invalid name option: {0}".format(k)) - - if not self.type_: - self.type_cmp = None - elif self.type_regex: - self.type_cmp = re.compile(self.type_) - else: - self.type_cmp = self.policy.lookup_type(type_) - - def set_range(self, range_, **opts): - """ - Set the criteria for matching the context's range. - - Parameter: - range_ Criteria to match the context's range. - - Keyword Parameters: - subset If true, the criteria will match if it is a subset - of the context's range. - overlap If true, the criteria will match if it overlaps - any of the context's range. - superset If true, the criteria will match if it is a superset - of the context's range. - proper If true, use proper superset/subset operations. - No effect if not using set operations. - - Exceptions: - NameError Invalid keyword option. - """ - - self.range_ = range_ - - for k in list(opts.keys()): - if k == "subset": - self.range_subset = opts[k] - elif k == "overlap": - self.range_overlap = opts[k] - elif k == "superset": - self.range_superset = opts[k] - elif k == "proper": - self.range_proper = opts[k] - else: - raise NameError("Invalid name option: {0}".format(k)) - - if self.range_: - self.range_cmp = self.policy.lookup_range(self.range_) - else: - self.range_cmp = None diff --git a/setools/descriptors.py b/setools/descriptors.py new file mode 100644 index 0000000..eab9210 --- /dev/null +++ b/setools/descriptors.py @@ -0,0 +1,230 @@ +# Copyright 2015, 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/>. +# +""" +SETools descriptors. + +These classes override how a class's attributes are get/set/deleted. +This is how the @property decorator works. + +See https://docs.python.org/3/howto/descriptor.html +for more details. +""" + +import re +from collections import defaultdict +from weakref import WeakKeyDictionary + +# +# Query criteria descriptors +# +# Implementation note: if the name_regex attribute value +# is changed the criteria must be reset. +# + + +class CriteriaDescriptor(object): + + """ + Single item criteria descriptor. + + Parameters: + name_regex The name of instance's regex setting attribute; + used as name_regex below. If unset, + regular expressions will never be used. + lookup_function The name of the SELinuxPolicy lookup function, + e.g. lookup_type or lookup_boolean. + default_value The default value of the criteria. The default + is None. + + Read-only instance attribute use (obj parameter): + policy The instance of SELinuxPolicy + name_regex This attribute is read to determine if + the criteria should be looked up or + compiled into a regex. If the attribute + does not exist, False is assumed. + """ + + def __init__(self, name_regex=None, lookup_function=None, default_value=None): + assert name_regex or lookup_function, "A simple attribute should be used if there is " \ + "no regex nor lookup function." + self.regex = name_regex + self.default_value = default_value + self.lookup_function = lookup_function + + # use weak references so instances can be + # garbage collected, rather than unnecessarily + # kept around due to this descriptor. + self.instances = WeakKeyDictionary() + + def __get__(self, obj, objtype=None): + if obj is None: + return self + + return self.instances.setdefault(obj, self.default_value) + + def __set__(self, obj, value): + if not value: + self.instances[obj] = None + elif self.regex and getattr(obj, self.regex, False): + self.instances[obj] = re.compile(value) + elif self.lookup_function: + lookup = getattr(obj.policy, self.lookup_function) + self.instances[obj] = lookup(value) + else: + self.instances[obj] = value + + +class CriteriaSetDescriptor(CriteriaDescriptor): + + """Descriptor for a set of criteria.""" + + def __set__(self, obj, value): + if not value: + self.instances[obj] = None + elif self.regex and getattr(obj, self.regex, False): + self.instances[obj] = re.compile(value) + elif self.lookup_function: + lookup = getattr(obj.policy, self.lookup_function) + self.instances[obj] = set(lookup(v) for v in value) + else: + self.instances[obj] = set(value) + + +class RuletypeDescriptor(object): + + """ + Descriptor for a list of rule types. + + Parameters: + validator The name of the SELinuxPolicy ruletype + validator function, e.g. validate_te_ruletype + default_value The default value of the criteria. The default + is None. + + Read-only instance attribute use (obj parameter): + policy The instance of SELinuxPolicy + """ + + def __init__(self, validator): + self.validator = validator + + # use weak references so instances can be + # garbage collected, rather than unnecessarily + # kept around due to this descriptor. + self.instances = WeakKeyDictionary() + + def __get__(self, obj, objtype=None): + if obj is None: + return self + + return self.instances.setdefault(obj, None) + + def __set__(self, obj, value): + if value: + validate = getattr(obj.policy, self.validator) + validate(value) + self.instances[obj] = value + else: + self.instances[obj] = None + + +# +# NetworkX Graph Descriptors +# +# These descriptors are used to simplify all +# of the dictionary use in the NetworkX graph. +# + + +class NetworkXGraphEdgeDescriptor(object): + + """ + Descriptor base class for NetworkX graph edge attributes. + + Parameter: + name The edge property name + + Instance class attribute use (obj parameter): + G The NetworkX graph + source The edge's source node + target The edge's target node + """ + + def __init__(self, propname): + self.name = propname + + def __get__(self, obj, objtype=None): + if obj is None: + return self + + return obj.G[obj.source][obj.target][self.name] + + def __set__(self, obj, value): + raise NotImplementedError + + def __delete__(self, obj): + raise NotImplementedError + + +class EdgeAttrDict(NetworkXGraphEdgeDescriptor): + + """A descriptor for edge attributes that are dictionaries.""" + + def __set__(self, obj, value): + # None is a special value to initialize the attribute + if value is None: + obj.G[obj.source][obj.target][self.name] = defaultdict(list) + else: + raise ValueError("{0} dictionaries should not be assigned directly".format(self.name)) + + def __delete__(self, obj): + obj.G[obj.source][obj.target][self.name].clear() + + +class EdgeAttrIntMax(NetworkXGraphEdgeDescriptor): + + """ + A descriptor for edge attributes that are non-negative integers that always + keep the max assigned value until re-initialized. + """ + + def __set__(self, obj, value): + # None is a special value to initialize + if value is None: + obj.G[obj.source][obj.target][self.name] = 0 + else: + current_value = obj.G[obj.source][obj.target][self.name] + obj.G[obj.source][obj.target][self.name] = max(current_value, value) + + +class EdgeAttrList(NetworkXGraphEdgeDescriptor): + + """A descriptor for edge attributes that are lists.""" + + def __set__(self, obj, value): + # None is a special value to initialize + if value is None: + obj.G[obj.source][obj.target][self.name] = [] + else: + raise ValueError("{0} lists should not be assigned directly".format(self.name)) + + def __delete__(self, obj): + # in Python3 a .clear() function was added for lists + # keep this implementation for Python 2 compat + del obj.G[obj.source][obj.target][self.name][:] diff --git a/setools/dta.py b/setools/dta.py index d0cc573..271efc4 100644 --- a/setools/dta.py +++ b/setools/dta.py @@ -23,7 +23,7 @@ from collections import defaultdict, namedtuple import networkx as nx from networkx.exception import NetworkXError, NetworkXNoPath -from .infoflow import EdgeAttrList +from .descriptors import EdgeAttrDict, EdgeAttrList __all__ = ['DomainTransitionAnalysis'] @@ -55,37 +55,32 @@ class DomainTransitionAnalysis(object): self.log = logging.getLogger(self.__class__.__name__) self.policy = policy - self.set_exclude(exclude) - self.set_reverse(reverse) + self.exclude = exclude + self.reverse = reverse self.rebuildgraph = True self.rebuildsubgraph = True self.G = nx.DiGraph() self.subG = None - def set_reverse(self, reverse): - """ - Set forward/reverse DTA direction. + @property + def reverse(self): + return self._reverse - Parameter: - reverse If true, a reverse DTA is performed, otherwise a - forward DTA is performed. - """ - - self.reverse = bool(reverse) + @reverse.setter + def reverse(self, direction): + self._reverse = bool(direction) self.rebuildsubgraph = True - def set_exclude(self, exclude): - """ - Set the domains to exclude from the domain transition analysis. + @property + def exclude(self): + return self._exclude - Parameter: - exclude A list of types. - """ - - if exclude: - self.exclude = [self.policy.lookup_type(t) for t in exclude] + @exclude.setter + def exclude(self, types): + if types: + self._exclude = [self.policy.lookup_type(t) for t in types] else: - self.exclude = [] + self._exclude = None self.rebuildsubgraph = True @@ -558,32 +553,6 @@ class DomainTransitionAnalysis(object): self.log.info("Completed building subgraph.") -class EdgeAttrDict(object): - - """ - A descriptor for edge attributes that are dictionaries. - - Parameter: - name The edge property name - """ - - def __init__(self, propname): - self.name = propname - - def __get__(self, obj, type=None): - return obj.G[obj.source][obj.target][self.name] - - def __set__(self, obj, value): - # None is a special value to initialize the attribute - if value is None: - obj.G[obj.source][obj.target][self.name] = defaultdict(list) - else: - raise ValueError("{0} dictionaries should not be assigned directly".format(self.name)) - - def __delete__(self, obj): - obj.G[obj.source][obj.target][self.name].clear() - - class Edge(object): """ diff --git a/setools/fsusequery.py b/setools/fsusequery.py index 3055101..6825a45 100644 --- a/setools/fsusequery.py +++ b/setools/fsusequery.py @@ -20,67 +20,54 @@ import logging import re from . import contextquery +from .descriptors import CriteriaDescriptor, CriteriaSetDescriptor class FSUseQuery(contextquery.ContextQuery): - """Query fs_use_* statements.""" + """ + Query fs_use_* statements. - def __init__(self, policy, - ruletype=None, - fs=None, fs_regex=False, - user=None, user_regex=False, - role=None, role_regex=False, - type_=None, type_regex=False, - range_=None, range_overlap=False, range_subset=False, - range_superset=False, range_proper=False): - """ - Parameters: - policy The policy to query. + Parameter: + policy The policy to query. - ruletype The rule type(s) to match. - fs The criteria to match the file system type. - fs_regex If true, regular expression matching - will be used on the file system type. - user The criteria to match the context's user. - user_regex If true, regular expression matching - will be used on the user. - role The criteria to match the context's role. - role_regex If true, regular expression matching - will be used on the role. - type_ The criteria to match the context's type. - type_regex If true, regular expression matching - will be used on the type. - range_ The criteria to match the context's range. - range_subset If true, the criteria will match if it is a subset - of the context's range. - range_overlap If true, the criteria will match if it overlaps - any of the context's range. - range_superset If true, the criteria will match if it is a superset - of the context's range. - range_proper If true, use proper superset/subset operations. - No effect if not using set operations. - """ - self.log = logging.getLogger(self.__class__.__name__) + Keyword Parameters/Class attributes: + ruletype The rule type(s) to match. + fs The criteria to match the file system type. + fs_regex If true, regular expression matching + will be used on the file system type. + user The criteria to match the context's user. + user_regex If true, regular expression matching + will be used on the user. + role The criteria to match the context's role. + role_regex If true, regular expression matching + will be used on the role. + type_ The criteria to match the context's type. + type_regex If true, regular expression matching + will be used on the type. + range_ The criteria to match the context's range. + range_subset If true, the criteria will match if it is a subset + of the context's range. + range_overlap If true, the criteria will match if it overlaps + any of the context's range. + range_superset If true, the criteria will match if it is a superset + of the context's range. + range_proper If true, use proper superset/subset operations. + No effect if not using set operations. + """ - self.policy = policy - - self.set_ruletype(ruletype) - self.set_fs(fs, regex=fs_regex) - self.set_user(user, regex=user_regex) - self.set_role(role, regex=role_regex) - self.set_type(type_, regex=type_regex) - self.set_range(range_, overlap=range_overlap, subset=range_subset, - superset=range_superset, proper=range_proper) + ruletype = None + fs = CriteriaDescriptor("fs_regex") + fs_regex = False def results(self): """Generator which yields all matching fs_use_* statements.""" self.log.info("Generating results from {0.policy}".format(self)) self.log.debug("Ruletypes: {0.ruletype}".format(self)) - self.log.debug("FS: {0.fs_cmp!r}, regex: {0.fs_regex}".format(self)) - self.log.debug("User: {0.user_cmp!r}, regex: {0.user_regex}".format(self)) - self.log.debug("Role: {0.role_cmp!r}, regex: {0.role_regex}".format(self)) - self.log.debug("Type: {0.type_cmp!r}, regex: {0.type_regex}".format(self)) + self.log.debug("FS: {0.fs!r}, regex: {0.fs_regex}".format(self)) + self.log.debug("User: {0.user!r}, regex: {0.user_regex}".format(self)) + self.log.debug("Role: {0.role!r}, regex: {0.role_regex}".format(self)) + self.log.debug("Type: {0.type_!r}, regex: {0.type_regex}".format(self)) self.log.debug("Range: {0.range_!r}, subset: {0.range_subset}, overlap: {0.range_overlap}, " "superset: {0.range_superset}, proper: {0.range_proper}".format(self)) @@ -90,60 +77,11 @@ class FSUseQuery(contextquery.ContextQuery): if self.fs and not self._match_regex( fsu.fs, - self.fs_cmp, + self.fs, self.fs_regex): continue - if not self._match_context( - fsu.context, - self.user_cmp, - self.user_regex, - self.role_cmp, - self.role_regex, - self.type_cmp, - self.type_regex, - self.range_cmp, - self.range_subset, - self.range_overlap, - self.range_superset, - self.range_proper): + if not self._match_context(fsu.context): continue yield fsu - - def set_ruletype(self, ruletype): - """ - Set the rule types for the rule query. - - Parameter: - ruletype The rule types to match. - """ - - self.ruletype = ruletype - - def set_fs(self, fs, **opts): - """ - Set the criteria for matching the file system type. - - Parameter: - fs Name to match the file system. - regex If true, regular expression matching will be used. - - Exceptions: - NameError Invalid keyword option. - """ - - self.fs = fs - - for k in list(opts.keys()): - if k == "regex": - self.fs_regex = opts[k] - else: - raise NameError("Invalid name option: {0}".format(k)) - - if not self.fs: - self.fs_cmp = None - elif self.fs_regex: - self.fs_cmp = re.compile(self.fs) - else: - self.fs_cmp = self.fs diff --git a/setools/genfsconquery.py b/setools/genfsconquery.py index b697257..c67dfd6 100644 --- a/setools/genfsconquery.py +++ b/setools/genfsconquery.py @@ -20,168 +20,79 @@ import logging import re from . import contextquery +from .descriptors import CriteriaDescriptor class GenfsconQuery(contextquery.ContextQuery): - """Query genfscon statements.""" + """ + Query genfscon statements. - def __init__(self, policy, - fs=None, fs_regex=False, - path=None, path_regex=False, - filetype=None, - user=None, user_regex=False, - role=None, role_regex=False, - type_=None, type_regex=False, - range_=None, range_overlap=False, range_subset=False, - range_superset=False, range_proper=False): - """ - Parameters: - policy The policy to query. + Parameter: + policy The policy to query. - fs The criteria to match the file system type. - fs_regex If true, regular expression matching - will be used on the file system type. - path The criteria to match the path. - path_regex If true, regular expression matching - will be used on the path. - user The criteria to match the context's user. - user_regex If true, regular expression matching - will be used on the user. - role The criteria to match the context's role. - role_regex If true, regular expression matching - will be used on the role. - type_ The criteria to match the context's type. - type_regex If true, regular expression matching - will be used on the type. - range_ The criteria to match the context's range. - range_subset If true, the criteria will match if it is a subset - of the context's range. - range_overlap If true, the criteria will match if it overlaps - any of the context's range. - range_superset If true, the criteria will match if it is a superset - of the context's range. - range_proper If true, use proper superset/subset operations. - No effect if not using set operations. - """ - self.log = logging.getLogger(self.__class__.__name__) + Keyword Parameters/Class attributes: + fs The criteria to match the file system type. + fs_regex If true, regular expression matching + will be used on the file system type. + path The criteria to match the path. + path_regex If true, regular expression matching + will be used on the path. + user The criteria to match the context's user. + user_regex If true, regular expression matching + will be used on the user. + role The criteria to match the context's role. + role_regex If true, regular expression matching + will be used on the role. + type_ The criteria to match the context's type. + type_regex If true, regular expression matching + will be used on the type. + range_ The criteria to match the context's range. + range_subset If true, the criteria will match if it is a subset + of the context's range. + range_overlap If true, the criteria will match if it overlaps + any of the context's range. + range_superset If true, the criteria will match if it is a superset + of the context's range. + range_proper If true, use proper superset/subset operations. + No effect if not using set operations. + """ - self.policy = policy - - self.set_fs(fs, regex=fs_regex) - self.set_path(path, regex=path_regex) - self.set_filetype(filetype) - self.set_user(user, regex=user_regex) - self.set_role(role, regex=role_regex) - self.set_type(type_, regex=type_regex) - self.set_range(range_, overlap=range_overlap, subset=range_subset, - superset=range_superset, proper=range_proper) + filetype = None + fs = CriteriaDescriptor("fs_regex") + fs_regex = False + path = CriteriaDescriptor("path_regex") + path_regex = False def results(self): """Generator which yields all matching genfscons.""" self.log.info("Generating results from {0.policy}".format(self)) - self.log.debug("FS: {0.fs_cmp!r}, regex: {0.fs_regex}".format(self)) - self.log.debug("Path: {0.path_cmp!r}, regex: {0.path_regex}".format(self)) + self.log.debug("FS: {0.fs!r}, regex: {0.fs_regex}".format(self)) + self.log.debug("Path: {0.path!r}, regex: {0.path_regex}".format(self)) self.log.debug("Filetype: {0.filetype!r}".format(self)) - self.log.debug("User: {0.user_cmp!r}, regex: {0.user_regex}".format(self)) - self.log.debug("Role: {0.role_cmp!r}, regex: {0.role_regex}".format(self)) - self.log.debug("Type: {0.type_cmp!r}, regex: {0.type_regex}".format(self)) + self.log.debug("User: {0.user!r}, regex: {0.user_regex}".format(self)) + self.log.debug("Role: {0.role!r}, regex: {0.role_regex}".format(self)) + self.log.debug("Type: {0.type_!r}, regex: {0.type_regex}".format(self)) self.log.debug("Range: {0.range_!r}, subset: {0.range_subset}, overlap: {0.range_overlap}, " "superset: {0.range_superset}, proper: {0.range_proper}".format(self)) for genfs in self.policy.genfscons(): if self.fs and not self._match_regex( genfs.fs, - self.fs_cmp, + self.fs, self.fs_regex): continue if self.path and not self._match_regex( genfs.path, - self.path_cmp, + self.path, self.path_regex): continue if self.filetype and not self.filetype == genfs.filetype: continue - if not self._match_context( - genfs.context, - self.user_cmp, - self.user_regex, - self.role_cmp, - self.role_regex, - self.type_cmp, - self.type_regex, - self.range_cmp, - self.range_subset, - self.range_overlap, - self.range_superset, - self.range_proper): + if not self._match_context(genfs.context): continue yield genfs - - def set_fs(self, fs, **opts): - """ - Set the criteria for matching the file system type. - - Parameter: - fs Name to match the file system. - regex If true, regular expression matching will be used. - - Exceptions: - NameError Invalid keyword option. - """ - - self.fs = fs - - for k in list(opts.keys()): - if k == "regex": - self.fs_regex = opts[k] - else: - raise NameError("Invalid name option: {0}".format(k)) - - if not self.fs: - self.fs_cmp = None - elif self.fs_regex: - self.fs_cmp = re.compile(self.fs) - else: - self.fs_cmp = self.fs - - def set_filetype(self, filetype): - """ - Set the criteria for matching the file type. - - Parameter: - filetype File type to match (e.g. stat.S_IFBLK or stat.S_IFREG). - """ - - self.filetype = filetype - - def set_path(self, path, **opts): - """ - Set the criteria for matching the path. - - Parameter: - path Criteria to match the path. - regex If true, regular expression matching will be used. - - Exceptions: - NameError Invalid keyword option. - """ - - self.path = path - - for k in list(opts.keys()): - if k == "regex": - self.path_regex = opts[k] - else: - raise NameError("Invalid name option: {0}".format(k)) - - if not self.path: - self.path_cmp = None - elif self.path_regex: - self.path_cmp = re.compile(self.path) - else: - self.path_cmp = self.path diff --git a/setools/infoflow.py b/setools/infoflow.py index 39acb89..ea3ec32 100644 --- a/setools/infoflow.py +++ b/setools/infoflow.py @@ -23,6 +23,8 @@ from collections import namedtuple import networkx as nx from networkx.exception import NetworkXError, NetworkXNoPath +from .descriptors import EdgeAttrIntMax, EdgeAttrList + __all__ = ['InfoFlowAnalysis'] # Return values for the analysis @@ -36,7 +38,7 @@ class InfoFlowAnalysis(object): """Information flow analysis.""" - def __init__(self, policy, perm_map, minweight=1, exclude=None): + def __init__(self, policy, perm_map, min_weight=1, exclude=None): """ Parameters: policy The policy to analyze. @@ -50,59 +52,48 @@ class InfoFlowAnalysis(object): self.policy = policy - self.set_min_weight(minweight) - self.set_perm_map(perm_map) - self.set_exclude(exclude) + self.min_weight = min_weight + self.perm_map = perm_map + self.exclude = exclude self.rebuildgraph = True self.rebuildsubgraph = True self.G = nx.DiGraph() self.subG = None - def set_min_weight(self, weight): - """ - Set the minimum permission weight for the information flow analysis. + @property + def min_weight(self): + return self._min_weight - Parameter: - weight Minimum permission weight (1-10) - - Exceptions: - ValueError The minimum weight is not 1-10. - """ + @min_weight.setter + def min_weight(self, weight): if not 1 <= weight <= 10: raise ValueError( "Min information flow weight must be an integer 1-10.") - self.minweight = weight + self._min_weight = weight self.rebuildsubgraph = True - def set_perm_map(self, perm_map): - """ - Set the permission map used for the information flow analysis. - - Parameter: - perm_map The permission map. - - Exceptions: - TypeError The map is not a file path or permission map object. - """ - self.perm_map = perm_map + @property + def perm_map(self): + return self._perm_map + @perm_map.setter + def perm_map(self, perm_map): + self._perm_map = perm_map self.rebuildgraph = True self.rebuildsubgraph = True - def set_exclude(self, exclude): - """ - Set the types to exclude from the information flow analysis. + @property + def exclude(self): + return self._exclude - Parameter: - exclude A list of types. - """ - - if exclude: - self.exclude = [self.policy.lookup_type(t) for t in exclude] + @exclude.setter + def exclude(self, types): + if types: + self._exclude = [self.policy.lookup_type(t) for t in types] else: - self.exclude = [] + self._exclude = [] self.rebuildsubgraph = True @@ -344,7 +335,7 @@ class InfoFlowAnalysis(object): self.log.info("Building subgraph...") self.log.debug("Excluding {0!r}".format(self.exclude)) - self.log.debug("Min weight {0}".format(self.minweight)) + self.log.debug("Min weight {0}".format(self.min_weight)) # delete excluded types from subgraph nodes = [n for n in self.G.nodes() if n not in self.exclude] @@ -353,11 +344,11 @@ class InfoFlowAnalysis(object): # delete edges below minimum weight. # no need if weight is 1, since that # does not exclude any edges. - if self.minweight > 1: + if self.min_weight > 1: delete_list = [] for s, t in self.subG.edges_iter(): edge = Edge(self.subG, s, t) - if edge.weight < self.minweight: + if edge.weight < self.min_weight: delete_list.append(edge) self.subG.remove_edges_from(delete_list) @@ -366,59 +357,6 @@ class InfoFlowAnalysis(object): self.log.info("Completed building subgraph.") -class EdgeAttrList(object): - - """ - A descriptor for edge attributes that are lists. - - Parameter: - name The edge property name - """ - - def __init__(self, propname): - self.name = propname - - def __get__(self, obj, type=None): - return obj.G[obj.source][obj.target][self.name] - - def __set__(self, obj, value): - # None is a special value to initialize - if value is None: - obj.G[obj.source][obj.target][self.name] = [] - else: - raise ValueError("{0} lists should not be assigned directly".format(self.name)) - - def __delete__(self, obj): - # in Python3 a .clear() function was added for lists - # keep this implementation for Python 2 compat - del obj.G[obj.source][obj.target][self.name][:] - - -class EdgeAttrIntMax(object): - - """ - A descriptor for edge attributes that are non-negative integers that always - keep the max assigned value until re-initialized. - - Parameter: - name The edge property name - """ - - def __init__(self, propname): - self.name = propname - - def __get__(self, obj, type=None): - return obj.G[obj.source][obj.target][self.name] - - def __set__(self, obj, value): - # None is a special value to initialize - if value is None: - obj.G[obj.source][obj.target][self.name] = 0 - else: - current_value = obj.G[obj.source][obj.target][self.name] - obj.G[obj.source][obj.target][self.name] = max(current_value, value) - - class Edge(object): """ diff --git a/setools/initsidquery.py b/setools/initsidquery.py index 04a1632..1eb3790 100644 --- a/setools/initsidquery.py +++ b/setools/initsidquery.py @@ -24,79 +24,51 @@ from . import contextquery class InitialSIDQuery(compquery.ComponentQuery, contextquery.ContextQuery): - """Initial SID (context) query.""" + """ + Initial SID (Initial context) query. - def __init__(self, policy, - name=None, name_regex=False, - user=None, user_regex=False, - role=None, role_regex=False, - type_=None, type_regex=False, - range_=None, range_overlap=False, range_subset=False, - range_superset=False, range_proper=False): - """ - Parameters: - policy The policy to query. + Parameter: + policy The policy to query. - user The criteria to match the context's user. - user_regex If true, regular expression matching - will be used on the user. - role The criteria to match the context's role. - role_regex If true, regular expression matching - will be used on the role. - type_ The criteria to match the context's type. - type_regex If true, regular expression matching - will be used on the type. - range_ The criteria to match the context's range. - range_subset If true, the criteria will match if it is a subset - of the context's range. - range_overlap If true, the criteria will match if it overlaps - any of the context's range. - range_superset If true, the criteria will match if it is a superset - of the context's range. - range_proper If true, use proper superset/subset operations. - No effect if not using set operations. - """ - self.log = logging.getLogger(self.__class__.__name__) - - self.policy = policy - - self.set_name(name, regex=name_regex) - self.set_user(user, regex=user_regex) - self.set_role(role, regex=role_regex) - self.set_type(type_, regex=type_regex) - self.set_range(range_, overlap=range_overlap, subset=range_subset, - superset=range_superset, proper=range_proper) + Keyword Parameters/Class attributes: + name The Initial SID name to match. + name_regex If true, regular expression matching + will be used on the Initial SID name. + user The criteria to match the context's user. + user_regex If true, regular expression matching + will be used on the user. + role The criteria to match the context's role. + role_regex If true, regular expression matching + will be used on the role. + type_ The criteria to match the context's type. + type_regex If true, regular expression matching + will be used on the type. + range_ The criteria to match the context's range. + range_subset If true, the criteria will match if it is a subset + of the context's range. + range_overlap If true, the criteria will match if it overlaps + any of the context's range. + range_superset If true, the criteria will match if it is a superset + of the context's range. + range_proper If true, use proper superset/subset operations. + No effect if not using set operations. + """ def results(self): """Generator which yields all matching initial SIDs.""" self.log.info("Generating results from {0.policy}".format(self)) - self.log.debug("Name: {0.name_cmp!r}, regex: {0.name_regex}".format(self)) - self.log.debug("User: {0.user_cmp!r}, regex: {0.user_regex}".format(self)) - self.log.debug("Role: {0.role_cmp!r}, regex: {0.role_regex}".format(self)) - self.log.debug("Type: {0.type_cmp!r}, regex: {0.type_regex}".format(self)) + self.log.debug("Name: {0.name!r}, regex: {0.name_regex}".format(self)) + self.log.debug("User: {0.user!r}, regex: {0.user_regex}".format(self)) + self.log.debug("Role: {0.role!r}, regex: {0.role_regex}".format(self)) + self.log.debug("Type: {0.type_!r}, regex: {0.type_regex}".format(self)) self.log.debug("Range: {0.range_!r}, subset: {0.range_subset}, overlap: {0.range_overlap}, " "superset: {0.range_superset}, proper: {0.range_proper}".format(self)) for i in self.policy.initialsids(): - if self.name and not self._match_regex( - i, - self.name_cmp, - self.name_regex): + if not self._match_name(i): continue - if not self._match_context( - i.context, - self.user_cmp, - self.user_regex, - self.role_cmp, - self.role_regex, - self.type_cmp, - self.type_regex, - self.range_cmp, - self.range_subset, - self.range_overlap, - self.range_superset, - self.range_proper): + if not self._match_context(i.context): continue yield i diff --git a/setools/mixins.py b/setools/mixins.py index 561202f..a31d420 100644 --- a/setools/mixins.py +++ b/setools/mixins.py @@ -19,129 +19,73 @@ # pylint: disable=attribute-defined-outside-init,no-member import re +from .descriptors import CriteriaDescriptor, CriteriaSetDescriptor + class MatchAlias(object): """Mixin for matching an object's aliases.""" - def _match_alias(self, obj): - """Match the object to the alias criteria.""" - return self._match_in_set(obj, self.alias_cmp, self.alias_regex) + alias = CriteriaDescriptor("alias_regex") + alias_regex = False - def set_alias(self, alias, **opts): + def _match_alias(self, obj): """ - Set the criteria for the component's aliases. + Match the alias criteria Parameter: - alias Name to match the component's aliases. - - Keyword Options: - regex If true, regular expression matching will be used. - - Exceptions: - NameError Invalid keyword option. + obj An object with an alias generator method named "aliases" """ - self.alias = alias - - for k in list(opts.keys()): - if k == "regex": - self.alias_regex = opts[k] - else: - raise NameError("Invalid alias option: {0}".format(k)) - if not self.alias: - self.alias_cmp = None - elif self.alias_regex: - self.alias_cmp = re.compile(self.alias) - else: - self.alias_cmp = self.alias + # if there is no criteria, everything matches. + return True + + return self._match_in_set(obj.aliases(), self.alias, self.alias_regex) class MatchObjClass(object): """Mixin for matching an object's class.""" + tclass = CriteriaSetDescriptor("tclass_regex", "lookup_class") + tclass_regex = False + def _match_object_class(self, obj): - """Match the object class criteria""" - - if isinstance(self.tclass_cmp, set): - return obj in self.tclass_cmp - elif self.tclass_regex: - return bool(self.tclass_cmp.search(str(obj))) - else: - return obj == self.tclass_cmp - - def set_tclass(self, tclass, **opts): """ - Set the object class(es) for the rule query. + Match the object class criteria Parameter: - tclass The name of the object classes to match. - This must be a string if regular expression - matching is used. - - Keyword Options: - regex If true, use a regular expression for - matching the object class. If false, any - set intersection will match. - - Exceptions: - NameError Invalid keyword option. + obj An object with an object class attribute named "tclass" """ - self.tclass = tclass - - for k in list(opts.keys()): - if k == "regex": - self.tclass_regex = opts[k] - else: - raise NameError("Invalid object class option: {0}".format(k)) - if not self.tclass: - self.tclass_cmp = None + # if there is no criteria, everything matches. + return True elif self.tclass_regex: - self.tclass_cmp = re.compile(self.tclass) - elif isinstance(self.tclass, str): - self.tclass_cmp = self.policy.lookup_class(self.tclass) + return bool(self.tclass.search(str(obj.tclass))) else: - self.tclass_cmp = set(self.policy.lookup_class(c) for c in self.tclass) + return obj.tclass in self.tclass class MatchPermission(object): """Mixin for matching an object's permissions.""" - def _match_perms(self, obj): - """Match the object to the permission criteria.""" - return self._match_set(obj, self.perms_cmp, self.perms_equal) + perms = CriteriaSetDescriptor("perms_regex") + perms_equal = False + perms_regex = False - def set_perms(self, perms, **opts): + def _match_perms(self, obj): """ - Set the permission set for the TE rule query. + Match the permission criteria Parameter: - perms The permissions to match. - - Options: - equal If true, the permission set of the rule - must equal the permissions criteria to - match. If false, permission in the critera - will cause a rule match. - - Exceptions: - NameError Invalid permission set keyword option. + obj An object with a permission set class attribute named "perms" """ - self.perms = perms - - for k in list(opts.keys()): - if k == "equal": - self.perms_equal = opts[k] - else: - raise NameError("Invalid permission set option: {0}".format(k)) - if not self.perms: - self.perms_cmp = None - else: - self.perms_cmp = set(self.perms) + # if there is no criteria, everything matches. + return True + + return self._match_regex_or_set(obj.perms, self.perms, self.perms_equal, self.perms_regex) diff --git a/setools/mlsrulequery.py b/setools/mlsrulequery.py index 3a47620..3a9e1bf 100644 --- a/setools/mlsrulequery.py +++ b/setools/mlsrulequery.py @@ -18,53 +18,52 @@ # import logging -from . import rulequery +from . import mixins, query +from .descriptors import CriteriaDescriptor, CriteriaSetDescriptor, RuletypeDescriptor -class MLSRuleQuery(rulequery.RuleQuery): +class MLSRuleQuery(mixins.MatchObjClass, query.PolicyQuery): - """Query MLS rules.""" + """ + Query MLS rules. - def __init__(self, policy, - ruletype=None, - source=None, source_regex=False, - target=None, target_regex=False, - tclass=None, tclass_regex=False, - default=None, default_overlap=False, default_subset=False, - default_superset=False, default_proper=False): - """ - Parameters: - policy The policy to query. - ruletype The rule type(s) to match. - source The name of the source type/attribute to match. - source_regex If true, regular expression matching will - be used on the source type/attribute. - target The name of the target type/attribute to match. - target_regex If true, regular expression matching will - be used on the target type/attribute. - tclass The object class(es) to match. - tclass_regex If true, use a regular expression for - matching the rule's object class. - """ - self.log = logging.getLogger(self.__class__.__name__) + Parameter: + policy The policy to query. - self.policy = policy + Keyword Parameters/Class attributes: + ruletype The list of rule type(s) to match. + source The name of the source type/attribute to match. + source_regex If true, regular expression matching will + be used on the source type/attribute. + target The name of the target type/attribute to match. + target_regex If true, regular expression matching will + be used on the target type/attribute. + tclass The object class(es) to match. + tclass_regex If true, use a regular expression for + matching the rule's object class. + """ - self.set_ruletype(ruletype) - self.set_source(source, regex=source_regex) - self.set_target(target, regex=target_regex) - self.set_tclass(tclass, regex=tclass_regex) - self.set_default(default, overlap=default_overlap, subset=default_subset, - superset=default_superset, proper=default_proper) + ruletype = RuletypeDescriptor("validate_mls_ruletype") + source = CriteriaDescriptor("source_regex", "lookup_type_or_attr") + source_regex = False + target = CriteriaDescriptor("target_regex", "lookup_type_or_attr") + target_regex = False + tclass = CriteriaSetDescriptor("tclass_regex", "lookup_class") + tclass_regex = False + default = CriteriaDescriptor(lookup_function="lookup_range") + default_overlap = False + default_subset = False + default_superset = False + default_proper = False def results(self): """Generator which yields all matching MLS rules.""" self.log.info("Generating results from {0.policy}".format(self)) self.log.debug("Ruletypes: {0.ruletype}".format(self)) - self.log.debug("Source: {0.source_cmp!r}, regex: {0.source_regex}".format(self)) - self.log.debug("Target: {0.target_cmp!r}, regex: {0.target_regex}".format(self)) - self.log.debug("Class: {0.tclass_cmp!r}, regex: {0.tclass_regex}".format(self)) - self.log.debug("Default: {0.default_cmp!r}, overlap: {0.default_overlap}, " + self.log.debug("Source: {0.source!r}, regex: {0.source_regex}".format(self)) + self.log.debug("Target: {0.target!r}, regex: {0.target_regex}".format(self)) + self.log.debug("Class: {0.tclass!r}, regex: {0.tclass_regex}".format(self)) + self.log.debug("Default: {0.default!r}, overlap: {0.default_overlap}, " "subset: {0.default_subset}, superset: {0.default_superset}, " "proper: {0.default_proper}".format(self)) @@ -81,7 +80,7 @@ class MLSRuleQuery(rulequery.RuleQuery): # if self.source and not self._match_regex( rule.source, - self.source_cmp, + self.source, self.source_regex): continue @@ -90,14 +89,14 @@ class MLSRuleQuery(rulequery.RuleQuery): # if self.target and not self._match_regex( rule.target, - self.target_cmp, + self.target, self.target_regex): continue # # Matching on object class # - if self.tclass and not self._match_object_class(rule.tclass): + if not self._match_object_class(rule): continue # @@ -105,7 +104,7 @@ class MLSRuleQuery(rulequery.RuleQuery): # if self.default and not self._match_range( rule.default, - self.default_cmp, + self.default, self.default_subset, self.default_overlap, self.default_superset, @@ -114,55 +113,3 @@ class MLSRuleQuery(rulequery.RuleQuery): # if we get here, we have matched all available criteria yield rule - - def set_ruletype(self, ruletype): - """ - Set the rule types for the rule query. - - Parameter: - ruletype The rule types to match. - """ - if ruletype: - self.policy.validate_mls_ruletype(ruletype) - - self.ruletype = ruletype - - def set_default(self, default, **opts): - """ - Set the criteria for matching the rule's default range. - - Parameter: - default Criteria to match the rule's default range. - - Keyword Parameters: - subset If true, the criteria will match if it is a subset - of the rule's default range. - overlap If true, the criteria will match if it overlaps - any of the rule's default range. - superset If true, the criteria will match if it is a superset - of the rule's default range. - proper If true, use proper superset/subset operations. - No effect if not using set operations. - - Exceptions: - NameError Invalid keyword option. - """ - - self.default = default - - for k in list(opts.keys()): - if k == "subset": - self.default_subset = opts[k] - elif k == "overlap": - self.default_overlap = opts[k] - elif k == "superset": - self.default_superset = opts[k] - elif k == "proper": - self.default_proper = opts[k] - else: - raise NameError("Invalid name option: {0}".format(k)) - - if not self.default: - self.default_cmp = None - else: - self.default_cmp = self.policy.lookup_range(self.default) diff --git a/setools/netifconquery.py b/setools/netifconquery.py index f1cdf9e..30db977 100644 --- a/setools/netifconquery.py +++ b/setools/netifconquery.py @@ -24,79 +24,54 @@ from . import contextquery class NetifconQuery(compquery.ComponentQuery, contextquery.ContextQuery): - """Network interface context query.""" + """ + Network interface context query. - def __init__(self, policy, - name=None, name_regex=False, - user=None, user_regex=False, - role=None, role_regex=False, - type_=None, type_regex=False, - range_=None, range_overlap=False, range_subset=False, - range_superset=False, range_proper=False): - """ - Parameters: - policy The policy to query. + Parameter: + policy The policy to query. - user The criteria to match the context's user. - user_regex If true, regular expression matching - will be used on the user. - role The criteria to match the context's role. - role_regex If true, regular expression matching - will be used on the role. - type_ The criteria to match the context's type. - type_regex If true, regular expression matching - will be used on the type. - range_ The criteria to match the context's range. - range_subset If true, the criteria will match if it is a subset - of the context's range. - range_overlap If true, the criteria will match if it overlaps - any of the context's range. - range_superset If true, the criteria will match if it is a superset - of the context's range. - range_proper If true, use proper superset/subset operations. - No effect if not using set operations. - """ - self.log = logging.getLogger(self.__class__.__name__) - - self.policy = policy - - self.set_name(name, regex=name_regex) - self.set_user(user, regex=user_regex) - self.set_role(role, regex=role_regex) - self.set_type(type_, regex=type_regex) - self.set_range(range_, overlap=range_overlap, subset=range_subset, - superset=range_superset, proper=range_proper) + Keyword Parameters/Class attributes: + name The name of the network interface to match. + name_regex If true, regular expression matching will + be used for matching the name. + user The criteria to match the context's user. + user_regex If true, regular expression matching + will be used on the user. + role The criteria to match the context's role. + role_regex If true, regular expression matching + will be used on the role. + type_ The criteria to match the context's type. + type_regex If true, regular expression matching + will be used on the type. + range_ The criteria to match the context's range. + range_subset If true, the criteria will match if it is a subset + of the context's range. + range_overlap If true, the criteria will match if it overlaps + any of the context's range. + range_superset If true, the criteria will match if it is a superset + of the context's range. + range_proper If true, use proper superset/subset operations. + No effect if not using set operations. + """ def results(self): """Generator which yields all matching netifcons.""" self.log.info("Generating results from {0.policy}".format(self)) - self.log.debug("Name: {0.name_cmp!r}, regex: {0.name_regex}".format(self)) - self.log.debug("User: {0.user_cmp!r}, regex: {0.user_regex}".format(self)) - self.log.debug("Role: {0.role_cmp!r}, regex: {0.role_regex}".format(self)) - self.log.debug("Type: {0.type_cmp!r}, regex: {0.type_regex}".format(self)) + self.log.debug("Name: {0.name!r}, regex: {0.name_regex}".format(self)) + self.log.debug("User: {0.user!r}, regex: {0.user_regex}".format(self)) + self.log.debug("Role: {0.role!r}, regex: {0.role_regex}".format(self)) + self.log.debug("Type: {0.type_!r}, regex: {0.type_regex}".format(self)) self.log.debug("Range: {0.range_!r}, subset: {0.range_subset}, overlap: {0.range_overlap}, " "superset: {0.range_superset}, proper: {0.range_proper}".format(self)) for netif in self.policy.netifcons(): if self.name and not self._match_regex( netif.netif, - self.name_cmp, + self.name, self.name_regex): continue - if not self._match_context( - netif.context, - self.user_cmp, - self.user_regex, - self.role_cmp, - self.role_regex, - self.type_cmp, - self.type_regex, - self.range_cmp, - self.range_subset, - self.range_overlap, - self.range_superset, - self.range_proper): + if not self._match_context(netif.context): continue yield netif diff --git a/setools/nodeconquery.py b/setools/nodeconquery.py index e83049f..eb21d81 100644 --- a/setools/nodeconquery.py +++ b/setools/nodeconquery.py @@ -29,62 +29,82 @@ from . import contextquery class NodeconQuery(contextquery.ContextQuery): - """Query nodecon statements.""" + """ + Query nodecon statements. - def __init__(self, policy, - network=None, network_overlap=False, - ip_version=None, - user=None, user_regex=False, - role=None, role_regex=False, - type_=None, type_regex=False, - range_=None, range_overlap=False, range_subset=False, - range_superset=False, range_proper=False): - """ - Parameters: - policy The policy to query. + Parameter: + policy The policy to query. - network The network address. - network_overlap If true, the net will match if it overlaps with - the nodecon's network instead of equality. - user The criteria to match the context's user. - user_regex If true, regular expression matching - will be used on the user. - role The criteria to match the context's role. - role_regex If true, regular expression matching - will be used on the role. - type_ The criteria to match the context's type. - type_regex If true, regular expression matching - will be used on the type. - range_ The criteria to match the context's range. - range_subset If true, the criteria will match if it is a subset - of the context's range. - range_overlap If true, the criteria will match if it overlaps - any of the context's range. - range_superset If true, the criteria will match if it is a superset - of the context's range. - range_proper If true, use proper superset/subset operations. - No effect if not using set operations. - """ - self.log = logging.getLogger(self.__class__.__name__) + Keyword Parameters/Class attributes: + network The IPv4/IPv6 address or IPv4/IPv6 network address + with netmask, e.g. 192.168.1.0/255.255.255.0 or + "192.168.1.0/24". + network_overlap If true, the net will match if it overlaps with + the nodecon's network instead of equality. + ip_version The IP version of the nodecon to match. (socket.AF_INET + for IPv4 or socket.AF_INET6 for IPv6) + user The criteria to match the context's user. + user_regex If true, regular expression matching + will be used on the user. + role The criteria to match the context's role. + role_regex If true, regular expression matching + will be used on the role. + type_ The criteria to match the context's type. + type_regex If true, regular expression matching + will be used on the type. + range_ The criteria to match the context's range. + range_subset If true, the criteria will match if it is a subset + of the context's range. + range_overlap If true, the criteria will match if it overlaps + any of the context's range. + range_superset If true, the criteria will match if it is a superset + of the context's range. + range_proper If true, use proper superset/subset operations. + No effect if not using set operations. + """ - self.policy = policy + _network = None + network_overlap = False + _ip_version = None - self.set_network(network, overlap=network_overlap) - self.set_ip_version(ip_version) - self.set_user(user, regex=user_regex) - self.set_role(role, regex=role_regex) - self.set_type(type_, regex=type_regex) - self.set_range(range_, overlap=range_overlap, subset=range_subset, - superset=range_superset, proper=range_proper) + @property + def ip_version(self): + return self._ip_version + + @ip_version.setter + def ip_version(self, value): + if value: + if not (value == AF_INET or value == AF_INET6): + raise ValueError( + "The address family must be {0} for IPv4 or {1} for IPv6.". + format(AF_INET, AF_INET6)) + + self._ip_version = value + else: + self._ip_version = None + + @property + def network(self): + return self._network + + @network.setter + def network(self, value): + if value: + try: + self._network = ipaddress.ip_network(value) + except NameError: # pragma: no cover + raise RuntimeError("Nodecon IP address/network functions require Python 3.3+.") + else: + self._network = None def results(self): """Generator which yields all matching nodecons.""" self.log.info("Generating results from {0.policy}".format(self)) self.log.debug("Network: {0.network!r}, overlap: {0.network_overlap}".format(self)) - self.log.debug("Ver: {0.version}".format(self)) - self.log.debug("User: {0.user_cmp!r}, regex: {0.user_regex}".format(self)) - self.log.debug("Role: {0.role_cmp!r}, regex: {0.role_regex}".format(self)) - self.log.debug("Type: {0.type_cmp!r}, regex: {0.type_regex}".format(self)) + self.log.debug("IP Version: {0.ip_version}".format(self)) + self.log.debug("User: {0.user!r}, regex: {0.user_regex}".format(self)) + self.log.debug("Role: {0.role!r}, regex: {0.role_regex}".format(self)) + self.log.debug("Type: {0.type_!r}, regex: {0.type_regex}".format(self)) self.log.debug("Range: {0.range_!r}, subset: {0.range_subset}, overlap: {0.range_overlap}, " "superset: {0.range_superset}, proper: {0.range_proper}".format(self)) @@ -119,77 +139,10 @@ class NodeconQuery(contextquery.ContextQuery): if not net == self.network: continue - if self.version and self.version != nodecon.ip_version: + if self.ip_version and self.ip_version != nodecon.ip_version: continue - if not self._match_context( - nodecon.context, - self.user_cmp, - self.user_regex, - self.role_cmp, - self.role_regex, - self.type_cmp, - self.type_regex, - self.range_cmp, - self.range_subset, - self.range_overlap, - self.range_superset, - self.range_proper): + if not self._match_context(nodecon.context): continue yield nodecon - - def set_network(self, net, **opts): - """ - Set the criteria for matching the network. - - Parameter: - net String IPv4/IPv6 address or IPv4/IPv6 network address - with netmask, e.g. 192.168.1.0/255.255.255.0 or - "192.168.1.0/24". - - Keyword parameters: - overlap If true, the criteria will match if it overlaps with the - nodecon's network instead of equality. - - Exceptions: - NameError Invalid keyword parameter. - """ - - if net: - try: - self.network = ipaddress.ip_network(net) - except NameError: # pragma: no cover - raise RuntimeError("Nodecon IP address/network functions require Python 3.3+.") - else: - # ensure self.network is set - self.network = None - - for k in list(opts.keys()): - if k == "overlap": - self.network_overlap = opts[k] - else: - raise NameError("Invalid name option: {0}".format(k)) - - def set_ip_version(self, version): - """ - Set the criteria for matching the IP version. - - Parameter: - version The address family to match. (socket.AF_INET for - IPv4 or socket.AF_INET6 for IPv6) - - Exceptions: - ValueError Invalid address family number. - """ - - if version: - if not (version == AF_INET or version == AF_INET6): - raise ValueError( - "The address family must be {0} for IPv4 or {1} for IPv6.". - format(AF_INET, AF_INET6)) - - self.version = version - - else: - self.version = None diff --git a/setools/objclassquery.py b/setools/objclassquery.py index 293bce9..8f40df8 100644 --- a/setools/objclassquery.py +++ b/setools/objclassquery.py @@ -20,63 +20,63 @@ import logging import re from . import compquery +from .descriptors import CriteriaDescriptor, CriteriaSetDescriptor from .policyrep.exception import NoCommon class ObjClassQuery(compquery.ComponentQuery): - """Query object classes.""" + """ + Query object classes. - def __init__(self, policy, - name=None, name_regex=False, - common=None, common_regex=False, - perms=None, perms_equal=False, perms_regex=False, - perms_indirect=True): - """ - Parameters: - name The name of the object set to match. - name_regex If true, regular expression matching will - be used for matching the name. - common The name of the inherited common to match. - common_regex If true, regular expression matching will - be used for matching the common name. - perms The permissions to match. - perms_equal If true, only commons with permission sets - that are equal to the criteria will - match. Otherwise, any intersection - will match. - perms_regex If true, regular expression matching - will be used on the permission names instead - of set logic. - comparison will not be used. - perms_indirect If false, permissions inherited from a common - permission set not will be evaluated. Default - is true. - """ - self.log = logging.getLogger(self.__class__.__name__) + Parameter: + policy The policy to query. - self.policy = policy - self.set_name(name, regex=name_regex) - self.set_common(common, regex=common_regex) - self.set_perms(perms, regex=perms_regex, equal=perms_equal, indirect=perms_indirect) + Keyword Parameters/Class attributes: + name The name of the object set to match. + name_regex If true, regular expression matching will + be used for matching the name. + common The name of the inherited common to match. + common_regex If true, regular expression matching will + be used for matching the common name. + perms The permissions to match. + perms_equal If true, only commons with permission sets + that are equal to the criteria will + match. Otherwise, any intersection + will match. + perms_regex If true, regular expression matching + will be used on the permission names instead + of set logic. + comparison will not be used. + perms_indirect If false, permissions inherited from a common + permission set not will be evaluated. Default + is true. + """ + + common = CriteriaDescriptor("common_regex", "lookup_common") + common_regex = False + perms = CriteriaSetDescriptor("perms_regex") + perms_equal = False + perms_indirect = True + perms_regex = False def results(self): """Generator which yields all matching object classes.""" self.log.info("Generating results from {0.policy}".format(self)) - self.log.debug("Name: {0.name_cmp!r}, regex: {0.name_regex}".format(self)) - self.log.debug("Common: {0.common_cmp!r}, regex: {0.common_regex}".format(self)) - self.log.debug("Perms: {0.perms_cmp}, regex: {0.perms_regex}, " + self.log.debug("Name: {0.name!r}, regex: {0.name_regex}".format(self)) + self.log.debug("Common: {0.common!r}, regex: {0.common_regex}".format(self)) + self.log.debug("Perms: {0.perms}, regex: {0.perms_regex}, " "eq: {0.perms_equal}, indirect: {0.perms_indirect}".format(self)) for class_ in self.policy.classes(): - if self.name and not self._match_name(class_): + if not self._match_name(class_): continue if self.common: try: if not self._match_regex( class_.common, - self.common_cmp, + self.common, self.common_regex): continue except NoCommon: @@ -93,75 +93,9 @@ class ObjClassQuery(compquery.ComponentQuery): if not self._match_regex_or_set( perms, - self.perms_cmp, + self.perms, self.perms_equal, self.perms_regex): continue yield class_ - - def set_common(self, common, **opts): - """ - Set the criteria for matching the common's name. - - Parameter: - name Name to match the common's name. - regex If true, regular expression matching will be used. - - Exceptions: - NameError Invalid keyword option. - """ - - self.common = common - - for k in list(opts.keys()): - if k == "regex": - self.common_regex = opts[k] - else: - raise NameError("Invalid common option: {0}".format(k)) - - if not self.common: - self.common_cmp = None - elif self.common_regex: - self.common_cmp = re.compile(self.common) - else: - self.common_cmp = self.policy.lookup_common(self.common) - - def set_perms(self, perms, **opts): - """ - Set the criteria for the common's permissions. - - Parameter: - perms Name to match the common's permissions. - - Keyword Options: - regex If true, regular expression matching will be used. - equal If true, the permisison set of the common - must equal the permissions criteria to - match. If false, any intersection in the - critera will cause a match. - indirect If true, the permissions inherited from a common - permission set will be included. - - Exceptions: - NameError Invalid keyword option. - """ - - self.perms = perms - - for k in list(opts.keys()): - if k == "regex": - self.perms_regex = opts[k] - elif k == "equal": - self.perms_equal = opts[k] - elif k == "indirect": - self.perms_indirect = opts[k] - else: - raise NameError("Invalid permissions option: {0}".format(k)) - - if not self.perms: - self.perms_cmp = None - elif self.perms_regex: - self.perms_cmp = re.compile(self.perms) - else: - self.perms_cmp = self.perms diff --git a/setools/polcapquery.py b/setools/polcapquery.py index 433966b..e024b05 100644 --- a/setools/polcapquery.py +++ b/setools/polcapquery.py @@ -23,28 +23,25 @@ from . import compquery class PolCapQuery(compquery.ComponentQuery): - """Query SELinux policy capabilities""" + """ + Query SELinux policy capabilities - def __init__(self, policy, - name=None, name_regex=False): - """ - Parameters: - name The name of the policy capability to match. - name_regex If true, regular expression matching will - be used for matching the name. - """ - self.log = logging.getLogger(self.__class__.__name__) + Parameter: + policy The policy to query. - self.policy = policy - self.set_name(name, regex=name_regex) + Keyword Parameters/Class attributes: + name The name of the policy capability to match. + name_regex If true, regular expression matching will + be used for matching the name. + """ def results(self): """Generator which yields all matching policy capabilities.""" self.log.info("Generating results from {0.policy}".format(self)) - self.log.debug("Name: {0.name_cmp!r}, regex: {0.name_regex}".format(self)) + self.log.debug("Name: {0.name!r}, regex: {0.name_regex}".format(self)) for cap in self.policy.polcaps(): - if self.name and not self._match_name(cap): + if not self._match_name(cap): continue yield cap diff --git a/setools/portconquery.py b/setools/portconquery.py index 6fcd23d..798a828 100644 --- a/setools/portconquery.py +++ b/setools/portconquery.py @@ -25,179 +25,122 @@ from .policyrep.netcontext import port_range class PortconQuery(contextquery.ContextQuery): - """Port context query.""" + """ + Port context query. - def __init__(self, policy, - protocol=None, - ports=port_range(None, None), ports_subset=False, ports_overlap=False, - ports_superset=False, ports_proper=False, - user=None, user_regex=False, - role=None, role_regex=False, - type_=None, type_regex=False, - range_=None, range_overlap=False, range_subset=False, - range_superset=False, range_proper=False): - """ - Parameters: - policy The policy to query. + Parameter: + policy The policy to query. - Keyword Parameters: - protocol The protocol to match (socket.IPPROTO_TCP for - TCP or socket.IPPROTO_UDP for UDP) + Keyword Parameters/Class attributes: + protocol The protocol to match (socket.IPPROTO_TCP for + TCP or socket.IPPROTO_UDP for UDP) - ports A 2-tuple of the port range to match. (Set both to - the same value for a single port) - ports_subset If true, the criteria will match if it is a subset - of the portcon's range. - ports_overlap If true, the criteria will match if it overlaps - any of the portcon's range. - ports_superset If true, the criteria will match if it is a superset - of the portcon's range. - ports_proper If true, use proper superset/subset operations. - No effect if not using set operations. + ports A 2-tuple of the port range to match. (Set both to + the same value for a single port) + ports_subset If true, the criteria will match if it is a subset + of the portcon's range. + ports_overlap If true, the criteria will match if it overlaps + any of the portcon's range. + ports_superset If true, the criteria will match if it is a superset + of the portcon's range. + ports_proper If true, use proper superset/subset operations. + No effect if not using set operations. - user The criteria to match the context's user. - user_regex If true, regular expression matching - will be used on the user. + user The criteria to match the context's user. + user_regex If true, regular expression matching + will be used on the user. - role The criteria to match the context's role. - role_regex If true, regular expression matching - will be used on the role. + role The criteria to match the context's role. + role_regex If true, regular expression matching + will be used on the role. - type_ The criteria to match the context's type. - type_regex If true, regular expression matching - will be used on the type. + type_ The criteria to match the context's type. + type_regex If true, regular expression matching + will be used on the type. - range_ The criteria to match the context's range. - range_subset If true, the criteria will match if it is a subset - of the context's range. - range_overlap If true, the criteria will match if it overlaps - any of the context's range. - range_superset If true, the criteria will match if it is a superset - of the context's range. - range_proper If true, use proper superset/subset operations. - No effect if not using set operations. - """ - self.log = logging.getLogger(self.__class__.__name__) + range_ The criteria to match the context's range. + range_subset If true, the criteria will match if it is a subset + of the context's range. + range_overlap If true, the criteria will match if it overlaps + any of the context's range. + range_superset If true, the criteria will match if it is a superset + of the context's range. + range_proper If true, use proper superset/subset operations. + No effect if not using set operations. + """ - self.policy = policy + _protocol = None + _ports = None + ports_subset = False + ports_overlap = False + ports_superset = False + ports_proper = False - self.set_protocol(protocol) - self.set_ports(ports, subset=ports_subset, overlap=ports_overlap, - superset=ports_superset, proper=ports_proper) - self.set_user(user, regex=user_regex) - self.set_role(role, regex=role_regex) - self.set_type(type_, regex=type_regex) - self.set_range(range_, overlap=range_overlap, subset=range_subset, - superset=range_superset, proper=range_proper) + @property + def ports(self): + return self._ports + + @ports.setter + def ports(self, value): + pending_ports = port_range(*value) + + if all(pending_ports): + if pending_ports.low < 1 or pending_ports.high < 1: + raise ValueError("Port numbers must be positive: {0.low}-{0.high}". + format(pending_ports)) + + if pending_ports.low > pending_ports.high: + raise ValueError( + "The low port must be smaller than the high port: {0.low}-{0.high}". + format(pending_ports)) + + self._ports = pending_ports + else: + self._ports = None + + @property + def protocol(self): + return self._protocol + + @protocol.setter + def protocol(self, value): + if value: + if not (value == IPPROTO_TCP or value == IPPROTO_UDP): + raise ValueError( + "The protocol must be {0} for TCP or {1} for UDP.". + format(IPPROTO_TCP, IPPROTO_UDP)) + + self._protocol = value + else: + self._protocol = None def results(self): """Generator which yields all matching portcons.""" self.log.info("Generating results from {0.policy}".format(self)) - self.log.debug("Ports: {0.ports_cmp}, overlap: {0.ports_overlap}, " + self.log.debug("Ports: {0.ports}, overlap: {0.ports_overlap}, " "subset: {0.ports_subset}, superset: {0.ports_superset}, " "proper: {0.ports_proper}".format(self)) - self.log.debug("User: {0.user_cmp!r}, regex: {0.user_regex}".format(self)) - self.log.debug("Role: {0.role_cmp!r}, regex: {0.role_regex}".format(self)) - self.log.debug("Type: {0.type_cmp!r}, regex: {0.type_regex}".format(self)) + self.log.debug("User: {0.user!r}, regex: {0.user_regex}".format(self)) + self.log.debug("Role: {0.role!r}, regex: {0.role_regex}".format(self)) + self.log.debug("Type: {0.type_!r}, regex: {0.type_regex}".format(self)) self.log.debug("Range: {0.range_!r}, subset: {0.range_subset}, overlap: {0.range_overlap}, " "superset: {0.range_superset}, proper: {0.range_proper}".format(self)) for portcon in self.policy.portcons(): - if all(self.ports): - if not self._match_range( - portcon.ports, - self.ports_cmp, - self.ports_subset, - self.ports_overlap, - self.ports_superset, - self.ports_proper): - continue + if self.ports and not self._match_range( + portcon.ports, + self.ports, + self.ports_subset, + self.ports_overlap, + self.ports_superset, + self.ports_proper): + continue if self.protocol and self.protocol != portcon.protocol: continue - if not self._match_context( - portcon.context, - self.user_cmp, - self.user_regex, - self.role_cmp, - self.role_regex, - self.type_cmp, - self.type_regex, - self.range_cmp, - self.range_subset, - self.range_overlap, - self.range_superset, - self.range_proper): + if not self._match_context(portcon.context): continue yield portcon - - def set_ports(self, ports, **opts): - """ - Set the criteria for matching the port range. - - Parameter: - ports A 2-tuple of the port range to match. (Set both to - the same value to match a single port) - - Keyword Parameters: - subset If true, the criteria will match if it is a subset - of the portcon's range. - overlap If true, the criteria will match if it overlaps - any of the portcon's range. - superset If true, the criteria will match if it is a superset - of the portcon's range. - proper If true, use proper superset/subset operations. - No effect if not using set operations. - """ - - self.ports = ports - - for k in list(opts.keys()): - if k == "subset": - self.ports_subset = opts[k] - elif k == "overlap": - self.ports_overlap = opts[k] - elif k == "superset": - self.ports_superset = opts[k] - elif k == "proper": - self.ports_proper = opts[k] - else: - raise NameError("Invalid name option: {0}".format(k)) - - if not all(self.ports): - self.ports_cmp = None - else: - if self.ports[0] < 1 or self.ports[1] < 1: - raise ValueError("Port numbers must be positive: {0[0]}-{0[1]}".format(ports)) - - if self.ports[0] > self.ports[1]: - raise ValueError( - "The low port must be smaller than the high port: {0[0]}-{0[1]}".format(ports)) - - self.ports_cmp = port_range(*ports) - - def set_protocol(self, protocol): - """ - Set the criteria for matching the IP protocol. - - Parameter: - version The protocol number to match. (socket.IPPROTO_TCP for - TCP or socket.IPPROTO_UDP for UDP) - - Exceptions: - ValueError Invalid protocol number. - """ - - if protocol: - if not (protocol == IPPROTO_TCP or protocol == IPPROTO_UDP): - raise ValueError( - "The protocol must be {0} for TCP or {1} for UDP.". - format(IPPROTO_TCP, IPPROTO_UDP)) - - self.protocol = protocol - - else: - self.protocol = None diff --git a/setools/query.py b/setools/query.py index 820dee8..358a095 100644 --- a/setools/query.py +++ b/setools/query.py @@ -16,11 +16,29 @@ # License along with SETools. If not, see # <http://www.gnu.org/licenses/>. # +import logging class PolicyQuery(object): - """Abstract base class for SELinux policy queries.""" + """Base class for SELinux policy queries.""" + + def __init__(self, policy, **kwargs): + self.log = logging.getLogger(self.__class__.__name__) + + self.policy = policy + + # keys are sorted in reverse order so regex settings + # are set before the criteria, e.g. name_regex + # is set before name. This ensures correct behavior + # since the criteria descriptors are sensitve to + # regex settings. + for name in sorted(kwargs.keys(), reverse=True): + attr = getattr(self, name, None) # None is not callable + if callable(attr): + raise ValueError("Keyword parameter {0} conflicts with a callable.".format(name)) + + setattr(self, name, kwargs[name]) @staticmethod def _match_regex(obj, criteria, regex): @@ -72,6 +90,24 @@ class PolicyQuery(object): else: return criteria in obj + @staticmethod + def _match_indirect_regex(obj, criteria, indirect, regex): + """ + Match the object with optional regular expression and indirection. + + Parameters: + obj The object to match. + criteria The criteria to match. + regex If regular expression matching should be used. + indirect If object indirection should be used, e.g. + expanding an attribute. + """ + + if indirect: + return PolicyQuery._match_in_set((obj.expand()), criteria, regex) + else: + return PolicyQuery._match_regex(obj, criteria, regex) + @staticmethod def _match_regex_or_set(obj, criteria, equal, regex): """ diff --git a/setools/rbacrulequery.py b/setools/rbacrulequery.py index 4d8e5ea..240b921 100644 --- a/setools/rbacrulequery.py +++ b/setools/rbacrulequery.py @@ -19,64 +19,79 @@ import logging import re +from . import mixins, query +from .descriptors import CriteriaDescriptor, CriteriaSetDescriptor, RuletypeDescriptor from .policyrep.exception import InvalidType, RuleUseError -from . import rulequery +class RBACRuleQuery(mixins.MatchObjClass, query.PolicyQuery): -class RBACRuleQuery(rulequery.RuleQuery): + """ + Query the RBAC rules. - """Query the RBAC rules.""" + Parameter: + policy The policy to query. - def __init__(self, policy, - ruletype=None, - source=None, source_regex=False, source_indirect=True, - target=None, target_regex=False, target_indirect=True, - tclass=None, tclass_regex=False, - default=None, default_regex=False): - """ - Parameters: - policy The policy to query. - ruletype The rule type(s) to match. - source The name of the source role/attribute to match. - source_indirect If true, members of an attribute will be - matched rather than the attribute itself. - source_regex If true, regular expression matching will - be used on the source role/attribute. - Obeys the source_indirect option. - target The name of the target role/attribute to match. - target_indirect If true, members of an attribute will be - matched rather than the attribute itself. - target_regex If true, regular expression matching will - be used on the target role/attribute. - Obeys target_indirect option. - tclass The object class(es) to match. - tclass_regex If true, use a regular expression for - matching the rule's object class. - default The name of the default role to match. - default_regex If true, regular expression matching will - be used on the default role. - """ - self.log = logging.getLogger(self.__class__.__name__) + Keyword Parameters/Class attributes: + ruletype The list of rule type(s) to match. + source The name of the source role/attribute to match. + source_indirect If true, members of an attribute will be + matched rather than the attribute itself. + source_regex If true, regular expression matching will + be used on the source role/attribute. + Obeys the source_indirect option. + target The name of the target role/attribute to match. + target_indirect If true, members of an attribute will be + matched rather than the attribute itself. + target_regex If true, regular expression matching will + be used on the target role/attribute. + Obeys target_indirect option. + tclass The object class(es) to match. + tclass_regex If true, use a regular expression for + matching the rule's object class. + default The name of the default role to match. + default_regex If true, regular expression matching will + be used on the default role. + """ - self.policy = policy + ruletype = RuletypeDescriptor("validate_rbac_ruletype") + source = CriteriaDescriptor("source_regex", "lookup_role") + source_regex = False + source_indirect = True + _target = None + target_regex = False + target_indirect = True + tclass = CriteriaSetDescriptor("tclass_regex", "lookup_class") + tclass_regex = False + default = CriteriaDescriptor("default_regex", "lookup_role") + default_regex = False - self.set_ruletype(ruletype) - self.set_source(source, indirect=source_indirect, regex=source_regex) - self.set_target(target, indirect=target_indirect, regex=target_regex) - self.set_tclass(tclass, regex=tclass_regex) - self.set_default(default, regex=default_regex) + @property + def target(self): + return self._target + + @target.setter + def target(self, value): + if not value: + self._target = None + elif self.target_regex: + self._target = re.compile(value) + else: + try: + self._target = self.policy.lookup_type_or_attr(value) + except InvalidType: + self._target = self.policy.lookup_role(value) def results(self): """Generator which yields all matching RBAC rules.""" self.log.info("Generating results from {0.policy}".format(self)) self.log.debug("Ruletypes: {0.ruletype}".format(self)) - self.log.debug("Source: {0.source_cmp!r}, indirect: {0.source_indirect}, " + self.log.debug("Source: {0.source!r}, indirect: {0.source_indirect}, " "regex: {0.source_regex}".format(self)) - self.log.debug("Target: {0.target_cmp!r}, indirect: {0.target_indirect}, " + self.log.debug("Target: {0.target!r}, indirect: {0.target_indirect}, " "regex: {0.target_regex}".format(self)) - self.log.debug("Class: {0.tclass_cmp!r}, regex: {0.tclass_regex}".format(self)) - self.log.debug("Default: {0.default_cmp!r}, regex: {0.default_regex}".format(self)) + self.log.debug("Class: {0.tclass!r}, regex: {0.tclass_regex}".format(self)) + self.log.debug("Default: {0.default!r}, regex: {0.default_regex}".format(self)) for rule in self.policy.rbacrules(): # @@ -91,7 +106,7 @@ class RBACRuleQuery(rulequery.RuleQuery): # if self.source and not self._match_indirect_regex( rule.source, - self.source_cmp, + self.source, self.source_indirect, self.source_regex): continue @@ -101,7 +116,7 @@ class RBACRuleQuery(rulequery.RuleQuery): # if self.target and not self._match_indirect_regex( rule.target, - self.target_cmp, + self.target, self.target_indirect, self.target_regex): continue @@ -109,12 +124,11 @@ class RBACRuleQuery(rulequery.RuleQuery): # # Matching on object class # - if self.tclass: - try: - if not self._match_object_class(rule.tclass): - continue - except RuleUseError: + try: + if not self._match_object_class(rule): continue + except RuleUseError: + continue # # Matching on default role @@ -123,7 +137,7 @@ class RBACRuleQuery(rulequery.RuleQuery): try: if not self._match_regex( rule.default, - self.default_cmp, + self.default, self.default_regex): continue except RuleUseError: @@ -131,115 +145,3 @@ class RBACRuleQuery(rulequery.RuleQuery): # if we get here, we have matched all available criteria yield rule - - def set_ruletype(self, ruletype): - """ - Set the rule types for the rule query. - - Parameter: - ruletype The rule types to match. - """ - if ruletype: - self.policy.validate_rbac_ruletype(ruletype) - - self.ruletype = ruletype - - def set_source(self, source, **opts): - """ - Set the criteria for the rule's source. - - Parameter: - source Name to match the rule's source. - - Keyword Options: - indirect If true, members of an attribute will be - matched rather than the attribute itself. - regex If true, regular expression matching will - be used. Obeys the indirect option. - - Exceptions: - NameError Invalid keyword option. - """ - - self.source = source - - for k in list(opts.keys()): - if k == "indirect": - self.source_indirect = opts[k] - elif k == "regex": - self.source_regex = opts[k] - else: - raise NameError("Invalid source option: {0}".format(k)) - - if not self.source: - self.source_cmp = None - elif self.source_regex: - self.source_cmp = re.compile(self.source) - else: - self.source_cmp = self.policy.lookup_role(self.source) - - def set_target(self, target, **opts): - """ - Set the criteria for the rule's target. - - Parameter: - target Name to match the rule's target. - - Keyword Options: - indirect If true, members of an attribute will be - matched rather than the attribute itself. - regex If true, regular expression matching will - be used. Obeys the indirect option. - - Exceptions: - NameError Invalid keyword option. - """ - - self.target = target - - for k in list(opts.keys()): - if k == "indirect": - self.target_indirect = opts[k] - elif k == "regex": - self.target_regex = opts[k] - else: - raise NameError("Invalid target option: {0}".format(k)) - - if not self.target: - self.target_cmp = None - elif self.target_regex: - self.target_cmp = re.compile(self.target) - else: - try: - self.target_cmp = self.policy.lookup_type_or_attr(self.target) - except InvalidType: - self.target_cmp = self.policy.lookup_role(self.target) - - def set_default(self, default, **opts): - """ - Set the criteria for the rule's default role. - - Parameter: - default Name to match the rule's default role. - - Keyword Options: - regex If true, regular expression matching will be used. - - Exceptions: - NameError Invalid keyword option. - """ - - self.default = default - - for k in list(opts.keys()): - if k == "regex": - self.default_regex = opts[k] - else: - raise NameError("Invalid default option: {0}".format(k)) - - if not self.default: - self.default_cmp = None - elif self.default_regex: - self.default_cmp = re.compile(self.default) - else: - self.default_cmp = self.policy.lookup_role(self.default) diff --git a/setools/rolequery.py b/setools/rolequery.py index 68b7b7a..e95dfa6 100644 --- a/setools/rolequery.py +++ b/setools/rolequery.py @@ -20,41 +20,40 @@ import logging import re from . import compquery +from .descriptors import CriteriaSetDescriptor class RoleQuery(compquery.ComponentQuery): - """Query SELinux policy roles.""" + """ + Query SELinux policy roles. - def __init__(self, policy, - name=None, name_regex=False, - types=None, types_equal=False, types_regex=False): - """ - Parameter: - policy The policy to query. - name The role name to match. - name_regex If true, regular expression matching - will be used on the role names. - types The type to match. - types_equal If true, only roles with type sets - that are equal to the criteria will - match. Otherwise, any intersection - will match. - types_regex If true, regular expression matching - will be used on the type names instead - of set logic. - """ - self.log = logging.getLogger(self.__class__.__name__) + Parameter: + policy The policy to query. - self.policy = policy - self.set_name(name, regex=name_regex) - self.set_types(types, regex=types_regex, equal=types_equal) + Keyword Parameters/Class attributes: + name The role name to match. + name_regex If true, regular expression matching + will be used on the role names. + types The type to match. + types_equal If true, only roles with type sets + that are equal to the criteria will + match. Otherwise, any intersection + will match. + types_regex If true, regular expression matching + will be used on the type names instead + of set logic. + """ + + types = CriteriaSetDescriptor("types_regex", "lookup_type") + types_equal = False + types_regex = False def results(self): """Generator which yields all matching roles.""" self.log.info("Generating results from {0.policy}".format(self)) - self.log.debug("Name: {0.name_cmp!r}, regex: {0.name_regex}".format(self)) - self.log.debug("Types: {0.types_cmp!r}, regex: {0.types_regex}, " + self.log.debug("Name: {0.name!r}, regex: {0.name_regex}".format(self)) + self.log.debug("Types: {0.types!r}, regex: {0.types_regex}, " "eq: {0.types_equal}".format(self)) for r in self.policy.roles(): @@ -65,50 +64,14 @@ class RoleQuery(compquery.ComponentQuery): # will confuse, especially for set equality type queries. continue - if self.name and not self._match_name(r): + if not self._match_name(r): continue if self.types and not self._match_regex_or_set( set(r.types()), - self.types_cmp, + self.types, self.types_equal, self.types_regex): continue yield r - - def set_types(self, types, **opts): - """ - Set the criteria for the role's types. - - Parameter: - types Name to match the role's types. - - Keyword Options: - regex If true, regular expression matching will be used - instead of set logic. - equal If true, the type set of the role - must equal the attributes criteria to - match. If false, any intersection in the - critera will cause a rule match. - - Exceptions: - NameError Invalid keyword option. - """ - - self.types = types - - for k in list(opts.keys()): - if k == "regex": - self.types_regex = opts[k] - elif k == "equal": - self.types_equal = opts[k] - else: - raise NameError("Invalid types option: {0}".format(k)) - - if not self.types: - self.types_cmp = None - elif self.types_regex: - self.types_cmp = re.compile(self.types) - else: - self.types_cmp = set(self.policy.lookup_type(t) for t in self.types) diff --git a/setools/rulequery.py b/setools/rulequery.py deleted file mode 100644 index 7c0bbc1..0000000 --- a/setools/rulequery.py +++ /dev/null @@ -1,126 +0,0 @@ -# Copyright 2014-2015, 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/>. -# -# pylint: disable=no-member,attribute-defined-outside-init,abstract-method -import re - -from . import mixins -from .query import PolicyQuery - - -class RuleQuery(mixins.MatchObjClass, PolicyQuery): - - """Abstract base class for rule queries.""" - - @staticmethod - def _match_indirect_regex(obj, criteria, indirect, regex): - """ - Match the object with optional regular expression and indirection. - - Parameters: - obj The object to match. - criteria The criteria to match. - regex If regular expression matching should be used. - indirect If object indirection should be used, e.g. - expanding an attribute. - """ - - if indirect: - return PolicyQuery._match_in_set( - (obj.expand()), - criteria, - regex) - else: - return PolicyQuery._match_regex( - obj, - criteria, - regex) - - def set_ruletype(self, ruletype): - raise NotImplementedError - - def set_source(self, source, **opts): - """ - Set the criteria for the rule's source. - - Parameter: - source Name to match the rule's source. - - Keyword Options: - indirect If true, members of an attribute will be - matched rather than the attribute itself. - regex If true, regular expression matching will - be used. Obeys the indirect option. - - Exceptions: - NameError Invalid keyword option. - """ - - self.source = source - - for k in list(opts.keys()): - if k == "indirect": - self.source_indirect = opts[k] - elif k == "regex": - self.source_regex = opts[k] - else: - raise NameError("Invalid source option: {0}".format(k)) - - if not self.source: - self.source_cmp = None - elif self.source_regex: - self.source_cmp = re.compile(self.source) - else: - self.source_cmp = self.policy.lookup_type_or_attr(self.source) - - def set_target(self, target, **opts): - """ - Set the criteria for the rule's target. - - Parameter: - target Name to match the rule's target. - - Keyword Options: - indirect If true, members of an attribute will be - matched rather than the attribute itself. - regex If true, regular expression matching will - be used. Obeys the indirect option. - - Exceptions: - NameError Invalid keyword option. - """ - - self.target = target - - for k in list(opts.keys()): - if k == "indirect": - self.target_indirect = opts[k] - elif k == "regex": - self.target_regex = opts[k] - else: - raise NameError("Invalid target option: {0}".format(k)) - - if not self.target: - self.target_cmp = None - elif self.target_regex: - self.target_cmp = re.compile(self.target) - else: - self.target_cmp = self.policy.lookup_type_or_attr(self.target) - - def set_default(self, default, **opts): - raise NotImplementedError diff --git a/setools/sensitivityquery.py b/setools/sensitivityquery.py index cca8e21..a102836 100644 --- a/setools/sensitivityquery.py +++ b/setools/sensitivityquery.py @@ -20,49 +20,47 @@ import logging from . import compquery from . import mixins +from .descriptors import CriteriaDescriptor class SensitivityQuery(mixins.MatchAlias, compquery.ComponentQuery): - """Query MLS Sensitivities""" + """ + Query MLS Sensitivities - def __init__(self, policy, - name=None, name_regex=False, - alias=None, alias_regex=False, - sens=None, sens_dom=False, sens_domby=False): - """ - Parameters: - name The name of the category to match. - name_regex If true, regular expression matching will - be used for matching the name. - alias The alias name to match. - alias_regex If true, regular expression matching - will be used on the alias names. - sens The criteria to match the sensitivity by dominance. - sens_dom If true, the criteria will match if it dominates - the sensitivity. - sens_domby If true, the criteria will match if it is dominated - by the sensitivity. - """ - self.log = logging.getLogger(self.__class__.__name__) + Parameter: + policy The policy to query. - self.policy = policy - self.set_name(name, regex=name_regex) - self.set_alias(alias, regex=alias_regex) - self.set_sensitivity(sens, dom=sens_dom, domby=sens_domby) + Keyword Parameters/Class attributes: + name The name of the category to match. + name_regex If true, regular expression matching will + be used for matching the name. + alias The alias name to match. + alias_regex If true, regular expression matching + will be used on the alias names. + sens The criteria to match the sensitivity by dominance. + sens_dom If true, the criteria will match if it dominates + the sensitivity. + sens_domby If true, the criteria will match if it is dominated + by the sensitivity. + """ + + sens = CriteriaDescriptor(lookup_function="lookup_sensitivity") + sens_dom = False + sens_domby = False def results(self): """Generator which yields all matching sensitivities.""" self.log.info("Generating results from {0.policy}".format(self)) - self.log.debug("Name: {0.name_cmp!r}, regex: {0.name_regex}".format(self)) - self.log.debug("Alias: {0.alias_cmp}, regex: {0.alias_regex}".format(self)) + self.log.debug("Name: {0.name!r}, regex: {0.name_regex}".format(self)) + self.log.debug("Alias: {0.alias}, regex: {0.alias_regex}".format(self)) self.log.debug("Sens: {0.sens!r}, dom: {0.sens_dom}, domby: {0.sens_domby}".format(self)) for s in self.policy.sensitivities(): - if self.name and not self._match_name(s): + if not self._match_name(s): continue - if self.alias and not self._match_alias(s.aliases()): + if not self._match_alias(s): continue if self.sens and not self._match_level( @@ -74,33 +72,3 @@ class SensitivityQuery(mixins.MatchAlias, compquery.ComponentQuery): continue yield s - - def set_sensitivity(self, sens, **opts): - """ - Set the criteria for matching the sensitivity by dominance. - - Parameter: - sens Criteria to match the sensitivity. - - Keyword Parameters: - dom If true, the criteria will match if it - dominates the sensitivity. - domby If true, the criteria will match if it - is dominated by the sensitivity. - - Exceptions: - NameError Invalid keyword option. - """ - - if sens: - self.sens = self.policy.lookup_sensitivity(sens) - else: - self.sens = None - - for k in list(opts.keys()): - if k == "dom": - self.sens_dom = opts[k] - elif k == "domby": - self.sens_domby = opts[k] - else: - raise NameError("Invalid name option: {0}".format(k)) diff --git a/setools/terulequery.py b/setools/terulequery.py index 417a79e..7f3eccf 100644 --- a/setools/terulequery.py +++ b/setools/terulequery.py @@ -19,75 +19,89 @@ import logging import re +from . import mixins, query +from .descriptors import CriteriaDescriptor, CriteriaSetDescriptor, RuletypeDescriptor from .policyrep.exception import RuleUseError, RuleNotConditional -from . import mixins -from . import rulequery -class TERuleQuery(mixins.MatchPermission, rulequery.RuleQuery): +class TERuleQuery(mixins.MatchObjClass, mixins.MatchPermission, query.PolicyQuery): - """Query the Type Enforcement rules.""" + """ + Query the Type Enforcement rules. - def __init__(self, policy, - ruletype=None, - source=None, source_regex=False, source_indirect=True, - target=None, target_regex=False, target_indirect=True, - tclass=None, tclass_regex=False, - perms=None, perms_equal=False, - default=None, default_regex=False, - boolean=None, boolean_regex=False, boolean_equal=False): - """ - Parameter: - policy The policy to query. - ruletype The rule type(s) to match. - source The name of the source type/attribute to match. - source_indirect If true, members of an attribute will be - matched rather than the attribute itself. - source_regex If true, regular expression matching will - be used on the source type/attribute. - Obeys the source_indirect option. - target The name of the target type/attribute to match. - target_indirect If true, members of an attribute will be - matched rather than the attribute itself. - target_regex If true, regular expression matching will - be used on the target type/attribute. - Obeys target_indirect option. - tclass The object class(es) to match. - tclass_regex If true, use a regular expression for - matching the rule's object class. - perms The permission(s) to match. - perms_equal If true, the permission set of the rule - must exactly match the permissions - criteria. If false, any set intersection - will match. - default The name of the default type to match. - default_regex If true, regular expression matching will be - used on the default type. - """ - self.log = logging.getLogger(self.__class__.__name__) + Parameter: + policy The policy to query. - self.policy = policy + Keyword Parameters/Class attributes: + ruletype The list of rule type(s) to match. + source The name of the source type/attribute to match. + source_indirect If true, members of an attribute will be + matched rather than the attribute itself. + Default is true. + source_regex If true, regular expression matching will + be used on the source type/attribute. + Obeys the source_indirect option. + Default is false. + target The name of the target type/attribute to match. + target_indirect If true, members of an attribute will be + matched rather than the attribute itself. + Default is true. + target_regex If true, regular expression matching will + be used on the target type/attribute. + Obeys target_indirect option. + Default is false. + tclass The object class(es) to match. + tclass_regex If true, use a regular expression for + matching the rule's object class. + Default is false. + perms The set of permission(s) to match. + perms_equal If true, the permission set of the rule + must exactly match the permissions + criteria. If false, any set intersection + will match. + Default is false. + perms_regex If true, regular expression matching will be used + on the permission names instead of set logic. + default The name of the default type to match. + default_regex If true, regular expression matching will be + used on the default type. + Default is false. + boolean The set of boolean(s) to match. + boolean_regex If true, regular expression matching will be + used on the booleans. + Default is false. + boolean_equal If true, the booleans in the conditional + expression of the rule must exactly match the + criteria. If false, any set intersection + will match. Default is false. + """ - self.set_ruletype(ruletype) - self.set_source(source, indirect=source_indirect, regex=source_regex) - self.set_target(target, indirect=target_indirect, regex=target_regex) - self.set_tclass(tclass, regex=tclass_regex) - self.set_perms(perms, equal=perms_equal) - self.set_default(default, regex=default_regex) - self.set_boolean(boolean, regex=boolean_regex, equal=boolean_equal) + ruletype = RuletypeDescriptor("validate_te_ruletype") + source = CriteriaDescriptor("source_regex", "lookup_type_or_attr") + source_regex = False + source_indirect = True + target = CriteriaDescriptor("target_regex", "lookup_type_or_attr") + target_regex = False + target_indirect = True + default = CriteriaDescriptor("default_regex", "lookup_type") + default_regex = False + boolean = CriteriaSetDescriptor("boolean_regex", "lookup_boolean") + boolean_regex = False + boolean_equal = False def results(self): """Generator which yields all matching TE rules.""" self.log.info("Generating results from {0.policy}".format(self)) self.log.debug("Ruletypes: {0.ruletype}".format(self)) - self.log.debug("Source: {0.source_cmp!r}, indirect: {0.source_indirect}, " + self.log.debug("Source: {0.source!r}, indirect: {0.source_indirect}, " "regex: {0.source_regex}".format(self)) - self.log.debug("Target: {0.target_cmp!r}, indirect: {0.target_indirect}, " + self.log.debug("Target: {0.target!r}, indirect: {0.target_indirect}, " "regex: {0.target_regex}".format(self)) - self.log.debug("Class: {0.tclass_cmp!r}, regex: {0.tclass_regex}".format(self)) - self.log.debug("Perms: {0.perms_cmp}, eq: {0.perms_equal}".format(self)) - self.log.debug("Default: {0.default_cmp!r}, regex: {0.default_regex}".format(self)) - self.log.debug("Boolean: {0.boolean_cmp!r}, eq: {0.boolean_equal}, " + self.log.debug("Class: {0.tclass!r}, regex: {0.tclass_regex}".format(self)) + self.log.debug("Perms: {0.perms!r}, regex: {0.perms_regex}, eq: {0.perms_equal}". + format(self)) + self.log.debug("Default: {0.default!r}, regex: {0.default_regex}".format(self)) + self.log.debug("Boolean: {0.boolean!r}, eq: {0.boolean_equal}, " "regex: {0.boolean_regex}".format(self)) for rule in self.policy.terules(): @@ -103,7 +117,7 @@ class TERuleQuery(mixins.MatchPermission, rulequery.RuleQuery): # if self.source and not self._match_indirect_regex( rule.source, - self.source_cmp, + self.source, self.source_indirect, self.source_regex): continue @@ -113,7 +127,7 @@ class TERuleQuery(mixins.MatchPermission, rulequery.RuleQuery): # if self.target and not self._match_indirect_regex( rule.target, - self.target_cmp, + self.target, self.target_indirect, self.target_regex): continue @@ -121,18 +135,17 @@ class TERuleQuery(mixins.MatchPermission, rulequery.RuleQuery): # # Matching on object class # - if self.tclass and not self._match_object_class(rule.tclass): + if not self._match_object_class(rule): continue # # Matching on permission set # - if self.perms: - try: - if not self._match_perms(rule.perms): - continue - except RuleUseError: + try: + if not self._match_perms(rule): continue + except RuleUseError: + continue # # Matching on default type @@ -141,7 +154,7 @@ class TERuleQuery(mixins.MatchPermission, rulequery.RuleQuery): try: if not self._match_regex( rule.default, - self.default_cmp, + self.default, self.default_regex): continue except RuleUseError: @@ -154,7 +167,7 @@ class TERuleQuery(mixins.MatchPermission, rulequery.RuleQuery): try: if not self._match_regex_or_set( rule.conditional.booleans, - self.boolean_cmp, + self.boolean, self.boolean_equal, self.boolean_regex): continue @@ -163,76 +176,3 @@ class TERuleQuery(mixins.MatchPermission, rulequery.RuleQuery): # if we get here, we have matched all available criteria yield rule - - def set_boolean(self, boolean, **opts): - """ - Set the Boolean for the TE rule query. - - Parameter: - boolean The Boolean names to match in the TE rule - conditional expression. - - Options: - regex If true, regular expression matching will be used. - - Exceptions: - NameError Invalid permission set keyword option. - """ - - self.boolean = boolean - - for k in list(opts.keys()): - if k == "regex": - self.boolean_regex = opts[k] - elif k == "equal": - self.boolean_equal = opts[k] - else: - raise NameError("Invalid permission set option: {0}".format(k)) - - if not self.boolean: - self.boolean_cmp = None - elif self.boolean_regex: - self.boolean_cmp = re.compile(self.boolean) - else: - self.boolean_cmp = set(self.policy.lookup_boolean(b) for b in self.boolean) - - def set_ruletype(self, ruletype): - """ - Set the rule types for the rule query. - - Parameter: - ruletype The rule types to match. - """ - if ruletype: - self.policy.validate_te_ruletype(ruletype) - - self.ruletype = ruletype - - def set_default(self, default, **opts): - """ - Set the criteria for the rule's default type. - - Parameter: - default Name to match the rule's default type. - - Keyword Options: - regex If true, regular expression matching will be used. - - Exceptions: - NameError Invalid keyword option. - """ - - self.default = default - - for k in list(opts.keys()): - if k == "regex": - self.default_regex = opts[k] - else: - raise NameError("Invalid default option: {0}".format(k)) - - if not self.default: - self.default_cmp = None - elif self.default_regex: - self.default_cmp = re.compile(self.default) - else: - self.default_cmp = self.policy.lookup_type(self.default) diff --git a/setools/typeattrquery.py b/setools/typeattrquery.py index b816ece..a91026c 100644 --- a/setools/typeattrquery.py +++ b/setools/typeattrquery.py @@ -20,88 +20,51 @@ import logging import re from . import compquery +from .descriptors import CriteriaSetDescriptor class TypeAttributeQuery(compquery.ComponentQuery): - """Query SELinux policy type attributes.""" + """ + Query SELinux policy type attributes. - def __init__(self, policy, - name=None, name_regex=False, - types=None, types_equal=False, types_regex=False): - """ - Parameter: - policy The policy to query. - name The type name to match. - name_regex If true, regular expression matching - will be used on the type names. - types The type to match. - types_equal If true, only attributes with type sets - that are equal to the criteria will - match. Otherwise, any intersection - will match. - types_regex If true, regular expression matching - will be used on the type names instead - of set logic. - """ - self.log = logging.getLogger(self.__class__.__name__) + Parameter: + policy The policy to query. - self.policy = policy - self.set_name(name, regex=name_regex) - self.set_types(types, regex=types_regex, equal=types_equal) + Keyword Parameters/Class attributes: + name The type name to match. + name_regex If true, regular expression matching + will be used on the type names. + types The type to match. + types_equal If true, only attributes with type sets + that are equal to the criteria will + match. Otherwise, any intersection + will match. + types_regex If true, regular expression matching + will be used on the type names instead + of set logic. + """ + + types = CriteriaSetDescriptor("types_regex", "lookup_type") + types_equal = False + types_regex = False def results(self): """Generator which yields all matching types.""" self.log.info("Generating results from {0.policy}".format(self)) - self.log.debug("Name: {0.name_cmp!r}, regex: {0.name_regex}".format(self)) - self.log.debug("Types: {0.types_cmp!r}, regex: {0.types_regex}, " + self.log.debug("Name: {0.name!r}, regex: {0.name_regex}".format(self)) + self.log.debug("Types: {0.types!r}, regex: {0.types_regex}, " "eq: {0.types_equal}".format(self)) for attr in self.policy.typeattributes(): - if self.name and not self._match_name(attr): + if not self._match_name(attr): continue if self.types and not self._match_regex_or_set( set(attr.expand()), - self.types_cmp, + self.types, self.types_equal, self.types_regex): continue yield attr - - def set_types(self, types, **opts): - """ - Set the criteria for the attribute's types. - - Parameter: - alias Name to match the component's types. - - Keyword Options: - regex If true, regular expression matching will be used - instead of set logic. - equal If true, the type set of the attribute - must equal the type criteria to - match. If false, any intersection in the - critera will cause a rule match. - - Exceptions: - NameError Invalid keyword option. - """ - - self.types = types - - for k in list(opts.keys()): - if k == "regex": - self.types_regex = opts[k] - elif k == "equal": - self.types_equal = opts[k] - else: - raise NameError("Invalid types option: {0}".format(k)) - - if not self.types: - self.types_cmp = None - elif self.types_regex: - self.types_cmp = re.compile(self.types) - else: - self.types_cmp = set(self.policy.lookup_type(t) for t in self.types) diff --git a/setools/typequery.py b/setools/typequery.py index 6484e48..6634f76 100644 --- a/setools/typequery.py +++ b/setools/typequery.py @@ -21,126 +21,76 @@ import re from . import compquery from . import mixins +from .descriptors import CriteriaSetDescriptor class TypeQuery(mixins.MatchAlias, compquery.ComponentQuery): - """Query SELinux policy types.""" + """ + Query SELinux policy types. - def __init__(self, policy, - name=None, name_regex=False, - alias=None, alias_regex=False, - attrs=None, attrs_equal=False, attrs_regex=False, - permissive=False, match_permissive=False): - """ - Parameter: - policy The policy to query. - name The type name to match. - name_regex If true, regular expression matching - will be used on the type names. - alias The alias name to match. - alias_regex If true, regular expression matching - will be used on the alias names. - attrs The attribute to match. - attrs_equal If true, only types with attribute sets - that are equal to the criteria will - match. Otherwise, any intersection - will match. - attrs_regex If true, regular expression matching - will be used on the attribute names instead - of set logic. - match_permissive If true, the permissive state will be matched. - permissive The permissive state to match. - """ - self.log = logging.getLogger(self.__class__.__name__) + Parameter: + policy The policy to query. - self.policy = policy - self.set_name(name, regex=name_regex) - self.set_alias(alias, regex=alias_regex) - self.set_attrs(attrs, regex=attrs_regex, equal=attrs_equal) - self.set_permissive(match_permissive, permissive=permissive) + Keyword Parameters/Class attributes: + name The type name to match. + name_regex If true, regular expression matching + will be used on the type names. + alias The alias name to match. + alias_regex If true, regular expression matching + will be used on the alias names. + attrs The attribute to match. + attrs_equal If true, only types with attribute sets + that are equal to the criteria will + match. Otherwise, any intersection + will match. + attrs_regex If true, regular expression matching + will be used on the attribute names instead + of set logic. + permissive The permissive state to match. If this + is None, the state is not matched. + """ + + attrs = CriteriaSetDescriptor("attrs_regex", "lookup_typeattr") + attrs_regex = False + attrs_equal = False + _permissive = None + + @property + def permissive(self): + return self._permissive + + @permissive.setter + def permissive(self, value): + if value is None: + self._permissive = None + else: + self._permissive = bool(value) def results(self): """Generator which yields all matching types.""" self.log.info("Generating results from {0.policy}".format(self)) - self.log.debug("Name: {0.name_cmp!r}, regex: {0.name_regex}".format(self)) - self.log.debug("Alias: {0.alias_cmp}, regex: {0.alias_regex}".format(self)) - self.log.debug("Attrs: {0.attrs_cmp!r}, regex: {0.attrs_regex}, " + self.log.debug("Name: {0.name!r}, regex: {0.name_regex}".format(self)) + self.log.debug("Alias: {0.alias}, regex: {0.alias_regex}".format(self)) + self.log.debug("Attrs: {0.attrs!r}, regex: {0.attrs_regex}, " "eq: {0.attrs_equal}".format(self)) - self.log.debug("Permissive: {0.match_permissive}, state: {0.permissive}".format(self)) + self.log.debug("Permissive: {0.permissive}".format(self)) for t in self.policy.types(): - if self.name and not self._match_name(t): + if not self._match_name(t): continue - if self.alias and not self._match_alias(t.aliases()): + if not self._match_alias(t): continue if self.attrs and not self._match_regex_or_set( set(t.attributes()), - self.attrs_cmp, + self.attrs, self.attrs_equal, self.attrs_regex): continue - if self.match_permissive and t.ispermissive != self.permissive: + if self.permissive is not None and t.ispermissive != self.permissive: continue yield t - - def set_attrs(self, attrs, **opts): - """ - Set the criteria for the type's attributes. - - Parameter: - alias Name to match the component's attributes. - - Keyword Options: - regex If true, regular expression matching will be used - instead of set logic. - equal If true, the attribute set of the type - must equal the attributes criteria to - match. If false, any intersection in the - critera will cause a rule match. - - Exceptions: - NameError Invalid keyword option. - """ - - self.attrs = attrs - - for k in list(opts.keys()): - if k == "regex": - self.attrs_regex = opts[k] - elif k == "equal": - self.attrs_equal = opts[k] - else: - raise NameError("Invalid alias option: {0}".format(k)) - - if not self.attrs: - self.attrs_cmp = None - elif self.attrs_regex: - self.attrs_cmp = re.compile(self.attrs) - else: - self.attrs_cmp = set(self.policy.lookup_typeattr(a) for a in self.attrs) - - def set_permissive(self, match, **opts): - """ - Set if the permissive state should be matched. - - Parameter: - match If true, the permissive state will be matched. - permissive If true, permissive types will match, otherwise - enforcing types will match. - - Exceptions: - NameError Invalid keyword option. - """ - - self.match_permissive = bool(match) - - for k in list(opts.keys()): - if k == "permissive": - self.permissive = bool(opts[k]) - else: - raise NameError("Invalid permissive option: {0}".format(k)) diff --git a/setools/userquery.py b/setools/userquery.py index 371071f..00910cf 100644 --- a/setools/userquery.py +++ b/setools/userquery.py @@ -20,63 +20,65 @@ import logging import re from . import compquery +from .descriptors import CriteriaDescriptor, CriteriaSetDescriptor class UserQuery(compquery.ComponentQuery): - """Query SELinux policy users.""" + """ + Query SELinux policy users. - def __init__(self, policy, - name=None, name_regex=False, - roles=None, roles_equal=False, roles_regex=False, - level=None, level_dom=False, level_domby=False, level_incomp=False, - range_=None, range_overlap=False, range_subset=False, - range_superset=False, range_proper=False): - """ - Parameter: - policy The policy to query. - name The user name to match. - name_regex If true, regular expression matching - will be used on the user names. - roles The attribute to match. - roles_equal If true, only types with role sets - that are equal to the criteria will - match. Otherwise, any intersection - will match. - roles_regex If true, regular expression matching - will be used on the role names instead - of set logic. - level The criteria to match the user's default level. - level_dom If true, the criteria will match if it dominates - the user's default level. - level_domby If true, the criteria will match if it is dominated - by the user's default level. - level_incomp If true, the criteria will match if it is incomparable - to the user's default level. - range_ The criteria to match the user's range. - range_subset If true, the criteria will match if it is a subset - of the user's range. - range_overlap If true, the criteria will match if it overlaps - any of the user's range. - range_superset If true, the criteria will match if it is a superset - of the user's range. - range_proper If true, use proper superset/subset operations. - No effect if not using set operations. - """ - self.log = logging.getLogger(self.__class__.__name__) + Parameter: + policy The policy to query. - self.policy = policy - self.set_name(name, regex=name_regex) - self.set_roles(roles, regex=roles_regex, equal=roles_equal) - self.set_level(level, dom=level_dom, domby=level_domby, incomp=level_incomp) - self.set_range(range_, overlap=range_overlap, subset=range_subset, - superset=range_superset, proper=range_proper) + Keyword Parameters/Class attributes: + name The user name to match. + name_regex If true, regular expression matching + will be used on the user names. + roles The attribute to match. + roles_equal If true, only types with role sets + that are equal to the criteria will + match. Otherwise, any intersection + will match. + roles_regex If true, regular expression matching + will be used on the role names instead + of set logic. + level The criteria to match the user's default level. + level_dom If true, the criteria will match if it dominates + the user's default level. + level_domby If true, the criteria will match if it is dominated + by the user's default level. + level_incomp If true, the criteria will match if it is incomparable + to the user's default level. + range_ The criteria to match the user's range. + range_subset If true, the criteria will match if it is a subset + of the user's range. + range_overlap If true, the criteria will match if it overlaps + any of the user's range. + range_superset If true, the criteria will match if it is a superset + of the user's range. + range_proper If true, use proper superset/subset operations. + No effect if not using set operations. + """ + + level = CriteriaDescriptor(lookup_function="lookup_level") + level_dom = False + level_domby = False + level_incomp = False + range_ = CriteriaDescriptor(lookup_function="lookup_range") + range_overlap = False + range_subset = False + range_superset = False + range_proper = False + roles = CriteriaSetDescriptor("roles_regex", "lookup_role") + roles_equal = False + roles_regex = False def results(self): """Generator which yields all matching users.""" self.log.info("Generating results from {0.policy}".format(self)) - self.log.debug("Name: {0.name_cmp!r}, regex: {0.name_regex}".format(self)) - self.log.debug("Roles: {0.roles_cmp!r}, regex: {0.roles_regex}, " + self.log.debug("Name: {0.name!r}, regex: {0.name_regex}".format(self)) + self.log.debug("Roles: {0.roles!r}, regex: {0.roles_regex}, " "eq: {0.roles_equal}".format(self)) self.log.debug("Level: {0.level!r}, dom: {0.level_dom}, domby: {0.level_domby}, " "incomp: {0.level_incomp}".format(self)) @@ -84,15 +86,12 @@ class UserQuery(compquery.ComponentQuery): "superset: {0.range_superset}, proper: {0.range_proper}".format(self)) for user in self.policy.users(): - if self.name and not self._match_regex( - user, - self.name_cmp, - self.name_regex): + if not self._match_name(user): continue if self.roles and not self._match_regex_or_set( user.roles, - self.roles_cmp, + self.roles, self.roles_equal, self.roles_regex): continue @@ -115,108 +114,3 @@ class UserQuery(compquery.ComponentQuery): continue yield user - - def set_level(self, level, **opts): - """ - Set the criteria for matching the user's default level. - - Parameter: - level Criteria to match the user's default level. - - Keyword Parameters: - dom If true, the criteria will match if it dominates the user's default level. - domby If true, the criteria will match if it is dominated by the user's default level. - incomp If true, the criteria will match if it incomparable to the user's default level. - - Exceptions: - NameError Invalid keyword option. - """ - - if level: - self.level = self.policy.lookup_level(level) - else: - self.level = None - - for k in list(opts.keys()): - if k == "dom": - self.level_dom = opts[k] - elif k == "domby": - self.level_domby = opts[k] - elif k == "incomp": - self.level_incomp = opts[k] - else: - raise NameError("Invalid name option: {0}".format(k)) - - def set_range(self, range_, **opts): - """ - Set the criteria for matching the user's range. - - Parameter: - range_ Criteria to match the user's range. - - Keyword Parameters: - subset If true, the criteria will match if it is a subset - of the user's range. - overlap If true, the criteria will match if it overlaps - any of the user's range. - superset If true, the criteria will match if it is a superset - of the user's range. - proper If true, use proper superset/subset operations. - No effect if not using set operations. - - Exceptions: - NameError Invalid keyword option. - """ - - if range_: - self.range_ = self.policy.lookup_range(range_) - else: - self.range_ = None - - for k in list(opts.keys()): - if k == "subset": - self.range_subset = opts[k] - elif k == "overlap": - self.range_overlap = opts[k] - elif k == "superset": - self.range_superset = opts[k] - elif k == "proper": - self.range_proper = opts[k] - else: - raise NameError("Invalid name option: {0}".format(k)) - - def set_roles(self, roles, **opts): - """ - Set the criteria for the users's roles. - - Parameter: - roles Name to match the component's attributes. - - Keyword Options: - regex If true, regular expression matching will be used - instead of set logic. - equal If true, the role set of the user - must equal the attributes criteria to - match. If false, any intersection in the - critera will cause a rule match. - - Exceptions: - NameError Invalid keyword option. - """ - - self.roles = roles - - for k in list(opts.keys()): - if k == "regex": - self.roles_regex = opts[k] - elif k == "equal": - self.roles_equal = opts[k] - else: - raise NameError("Invalid roles option: {0}".format(k)) - - if not self.roles: - self.roles_cmp = None - elif self.roles_regex: - self.roles_cmp = re.compile(self.roles) - else: - self.roles_cmp = set(self.policy.lookup_role(r) for r in self.roles) diff --git a/tests/boolquery.py b/tests/boolquery.py index 5dfba40..f2a1648 100644 --- a/tests/boolquery.py +++ b/tests/boolquery.py @@ -52,7 +52,7 @@ class BoolQueryTest(unittest.TestCase): def test_010_default(self): """Boolean query with default state match.""" - q = BoolQuery(self.p, match_default=True, default=False) + q = BoolQuery(self.p, default=False) bools = sorted(str(b) for b in q.results()) self.assertListEqual(["test10a", "test10b"], bools) diff --git a/tests/constraintquery.py b/tests/constraintquery.py index cc645b2..6923fcd 100644 --- a/tests/constraintquery.py +++ b/tests/constraintquery.py @@ -42,6 +42,7 @@ class ConstraintQueryTest(unittest.TestCase): constraint = sorted(c.tclass for c in q.results()) self.assertListEqual(["test1"], constraint) + @unittest.skip("Setting tclass to a string is no longer supported.") def test_010_class_exact(self): """Constraint query with exact object class match.""" q = ConstraintQuery(self.p, tclass="test10") diff --git a/tests/dta.py b/tests/dta.py index 7c07672..6004643 100644 --- a/tests/dta.py +++ b/tests/dta.py @@ -347,7 +347,7 @@ class DomainTransitionAnalysisTest(mixins.ValidateRule, unittest.TestCase): # Don't check node list since the disconnected nodes are not # removed after removing invalid domain transitions - self.a.set_reverse(False) + self.a.reverse = False self.a._build_subgraph() start = self.p.lookup_type("start") @@ -375,7 +375,7 @@ class DomainTransitionAnalysisTest(mixins.ValidateRule, unittest.TestCase): # Don't check node list since the disconnected nodes are not # removed after removing invalid domain transitions - self.a.set_reverse(True) + self.a.reverse = True self.a._build_subgraph() start = self.p.lookup_type("start") @@ -399,8 +399,8 @@ class DomainTransitionAnalysisTest(mixins.ValidateRule, unittest.TestCase): # Don't check node list since the disconnected nodes are not # removed after removing invalid domain transitions - self.a.set_reverse(False) - self.a.set_exclude(["trans1"]) + self.a.reverse = False + self.a.exclude = ["trans1"] self.a._build_subgraph() start = self.p.lookup_type("start") @@ -421,8 +421,8 @@ class DomainTransitionAnalysisTest(mixins.ValidateRule, unittest.TestCase): # Don't check node list since the disconnected nodes are not # removed after removing invalid domain transitions - self.a.set_reverse(False) - self.a.set_exclude(["trans3_exec1"]) + self.a.reverse = False + self.a.exclude = ["trans3_exec1"] self.a._build_subgraph() start = self.p.lookup_type("start") @@ -446,8 +446,8 @@ class DomainTransitionAnalysisTest(mixins.ValidateRule, unittest.TestCase): # Don't check node list since the disconnected nodes are not # removed after removing invalid domain transitions - self.a.set_reverse(False) - self.a.set_exclude(["bothtrans200_exec"]) + self.a.reverse = False + self.a.exclude = ["bothtrans200_exec"] self.a._build_subgraph() start = self.p.lookup_type("start") @@ -471,8 +471,8 @@ class DomainTransitionAnalysisTest(mixins.ValidateRule, unittest.TestCase): # Don't check node list since the disconnected nodes are not # removed after removing invalid domain transitions - self.a.set_reverse(False) - self.a.set_exclude(["trans2_exec"]) + self.a.reverse = False + self.a.exclude = ["trans2_exec"] self.a._build_subgraph() start = self.p.lookup_type("start") @@ -492,8 +492,8 @@ class DomainTransitionAnalysisTest(mixins.ValidateRule, unittest.TestCase): def test_300_all_paths(self): """DTA: all paths output""" - self.a.set_reverse(False) - self.a.set_exclude(None) + self.a.reverse = False + self.a.exclude = None expected_path = ["start", "dyntrans100", "bothtrans200"] @@ -533,8 +533,8 @@ class DomainTransitionAnalysisTest(mixins.ValidateRule, unittest.TestCase): def test_301_all_shortest_paths(self): """DTA: all shortest paths output""" - self.a.set_reverse(False) - self.a.set_exclude(None) + self.a.reverse = False + self.a.exclude = None expected_path = ["start", "dyntrans100", "bothtrans200"] @@ -574,8 +574,8 @@ class DomainTransitionAnalysisTest(mixins.ValidateRule, unittest.TestCase): def test_302_shortest_path(self): """DTA: shortest path output""" - self.a.set_reverse(False) - self.a.set_exclude(None) + self.a.reverse = False + self.a.exclude = None expected_path = ["start", "dyntrans100", "bothtrans200"] @@ -615,8 +615,8 @@ class DomainTransitionAnalysisTest(mixins.ValidateRule, unittest.TestCase): def test_303_transitions(self): """DTA: transitions output""" - self.a.set_reverse(False) - self.a.set_exclude(None) + self.a.reverse = False + self.a.exclude = None transitions = list(self.a.transitions("start")) self.assertEqual(2, len(transitions)) @@ -652,8 +652,8 @@ class DomainTransitionAnalysisTest(mixins.ValidateRule, unittest.TestCase): def test_310_all_paths_reversed(self): """DTA: all paths output reverse DTA""" - self.a.set_reverse(True) - self.a.set_exclude(None) + self.a.reverse = True + self.a.exclude = None expected_path = ["bothtrans200", "dyntrans100", "start"] @@ -693,8 +693,8 @@ class DomainTransitionAnalysisTest(mixins.ValidateRule, unittest.TestCase): def test_311_all_shortest_paths_reversed(self): """DTA: all shortest paths output reverse DTA""" - self.a.set_reverse(True) - self.a.set_exclude(None) + self.a.reverse = True + self.a.exclude = None expected_path = ["bothtrans200", "dyntrans100", "start"] @@ -734,8 +734,8 @@ class DomainTransitionAnalysisTest(mixins.ValidateRule, unittest.TestCase): def test_312_shortest_path_reversed(self): """DTA: shortest path output reverse DTA""" - self.a.set_reverse(True) - self.a.set_exclude(None) + self.a.reverse = True + self.a.exclude = None expected_path = ["bothtrans200", "dyntrans100", "start"] @@ -775,8 +775,8 @@ class DomainTransitionAnalysisTest(mixins.ValidateRule, unittest.TestCase): def test_313_transitions_reversed(self): """DTA: transitions output reverse DTA""" - self.a.set_reverse(True) - self.a.set_exclude(None) + self.a.reverse = True + self.a.exclude = None transitions = list(self.a.transitions("bothtrans200")) self.assertEqual(1, len(transitions)) @@ -812,160 +812,161 @@ class DomainTransitionAnalysisTest(mixins.ValidateRule, unittest.TestCase): def test_900_set_exclude_invalid_type(self): """DTA: set invalid excluded type.""" - self.a.set_reverse(False) - self.a.set_exclude(None) - self.assertRaises(InvalidType, self.a.set_exclude, ["trans1", "invalid_type"]) + self.a.reverse = False + self.a.exclude = None + with self.assertRaises(InvalidType): + self.a.exclude = ["trans1", "invalid_type"] def test_910_all_paths_invalid_source(self): """DTA: all paths with invalid source type.""" - self.a.set_reverse(False) - self.a.set_exclude(None) + self.a.reverse = False + self.a.exclude = None with self.assertRaises(InvalidType): list(self.a.all_paths("invalid_type", "trans1")) def test_911_all_paths_invalid_target(self): """DTA: all paths with invalid target type.""" - self.a.set_reverse(False) - self.a.set_exclude(None) + self.a.reverse = False + self.a.exclude = None with self.assertRaises(InvalidType): list(self.a.all_paths("trans1", "invalid_type")) def test_912_all_paths_invalid_maxlen(self): """DTA: all paths with invalid max path length.""" - self.a.set_reverse(False) - self.a.set_exclude(None) + self.a.reverse = False + self.a.exclude = None with self.assertRaises(ValueError): list(self.a.all_paths("trans1", "trans2", maxlen=-2)) def test_913_all_paths_source_excluded(self): """DTA: all paths with excluded source type.""" - self.a.set_reverse(False) - self.a.set_exclude(["trans1"]) + self.a.reverse = False + self.a.exclude = ["trans1"] paths = list(self.a.all_paths("trans1", "trans2")) self.assertEqual(0, len(paths)) def test_914_all_paths_target_excluded(self): """DTA: all paths with excluded target type.""" - self.a.set_reverse(False) - self.a.set_exclude(["trans2"]) + self.a.reverse = False + self.a.exclude = ["trans2"] paths = list(self.a.all_paths("trans1", "trans2")) self.assertEqual(0, len(paths)) def test_915_all_paths_source_disconnected(self): """DTA: all paths with disconnected source type.""" - self.a.set_reverse(False) - self.a.set_exclude(None) + self.a.reverse = False + self.a.exclude = None paths = list(self.a.all_paths("trans5", "trans2")) self.assertEqual(0, len(paths)) def test_916_all_paths_target_disconnected(self): """DTA: all paths with disconnected target type.""" - self.a.set_reverse(False) - self.a.set_exclude(["trans3"]) + self.a.reverse = False + self.a.exclude = ["trans3"] paths = list(self.a.all_paths("trans2", "trans5")) self.assertEqual(0, len(paths)) def test_920_shortest_path_invalid_source(self): """DTA: shortest path with invalid source type.""" - self.a.set_reverse(False) - self.a.set_exclude(None) + self.a.reverse = False + self.a.exclude = None with self.assertRaises(InvalidType): list(self.a.shortest_path("invalid_type", "trans1")) def test_921_shortest_path_invalid_target(self): """DTA: shortest path with invalid target type.""" - self.a.set_reverse(False) - self.a.set_exclude(None) + self.a.reverse = False + self.a.exclude = None with self.assertRaises(InvalidType): list(self.a.shortest_path("trans1", "invalid_type")) def test_922_shortest_path_source_excluded(self): """DTA: shortest path with excluded source type.""" - self.a.set_reverse(False) - self.a.set_exclude(["trans1"]) + self.a.reverse = False + self.a.exclude = ["trans1"] paths = list(self.a.shortest_path("trans1", "trans2")) self.assertEqual(0, len(paths)) def test_923_shortest_path_target_excluded(self): """DTA: shortest path with excluded target type.""" - self.a.set_reverse(False) - self.a.set_exclude(["trans2"]) + self.a.reverse = False + self.a.exclude = ["trans2"] paths = list(self.a.shortest_path("trans1", "trans2")) self.assertEqual(0, len(paths)) def test_924_shortest_path_source_disconnected(self): """DTA: shortest path with disconnected source type.""" - self.a.set_reverse(False) - self.a.set_exclude(None) + self.a.reverse = False + self.a.exclude = None paths = list(self.a.shortest_path("trans5", "trans2")) self.assertEqual(0, len(paths)) def test_925_shortest_path_target_disconnected(self): """DTA: shortest path with disconnected target type.""" - self.a.set_reverse(False) - self.a.set_exclude(["trans3"]) + self.a.reverse = False + self.a.exclude = ["trans3"] paths = list(self.a.shortest_path("trans2", "trans5")) self.assertEqual(0, len(paths)) def test_930_all_shortest_paths_invalid_source(self): """DTA: all shortest paths with invalid source type.""" - self.a.set_reverse(False) - self.a.set_exclude(None) + self.a.reverse = False + self.a.exclude = None with self.assertRaises(InvalidType): list(self.a.all_shortest_paths("invalid_type", "trans1")) def test_931_all_shortest_paths_invalid_target(self): """DTA: all shortest paths with invalid target type.""" - self.a.set_reverse(False) - self.a.set_exclude(None) + self.a.reverse = False + self.a.exclude = None with self.assertRaises(InvalidType): list(self.a.all_shortest_paths("trans1", "invalid_type")) def test_932_all_shortest_paths_source_excluded(self): """DTA: all shortest paths with excluded source type.""" - self.a.set_reverse(False) - self.a.set_exclude(["trans1"]) + self.a.reverse = False + self.a.exclude = ["trans1"] paths = list(self.a.all_shortest_paths("trans1", "trans2")) self.assertEqual(0, len(paths)) def test_933_all_shortest_paths_target_excluded(self): """DTA: all shortest paths with excluded target type.""" - self.a.set_reverse(False) - self.a.set_exclude(["trans2"]) + self.a.reverse = False + self.a.exclude = ["trans2"] paths = list(self.a.all_shortest_paths("trans1", "trans2")) self.assertEqual(0, len(paths)) def test_934_all_shortest_paths_source_disconnected(self): """DTA: all shortest paths with disconnected source type.""" - self.a.set_reverse(False) - self.a.set_exclude(None) + self.a.reverse = False + self.a.exclude = None paths = list(self.a.all_shortest_paths("trans5", "trans2")) self.assertEqual(0, len(paths)) def test_935_all_shortest_paths_target_disconnected(self): """DTA: all shortest paths with disconnected target type.""" - self.a.set_reverse(False) - self.a.set_exclude(["trans3"]) + self.a.reverse = False + self.a.exclude = ["trans3"] paths = list(self.a.all_shortest_paths("trans2", "trans5")) self.assertEqual(0, len(paths)) def test_940_transitions_invalid_source(self): """DTA: transitions with invalid source type.""" - self.a.set_reverse(False) - self.a.set_exclude(None) + self.a.reverse = False + self.a.exclude = None with self.assertRaises(InvalidType): list(self.a.transitions("invalid_type")) def test_941_transitions_source_excluded(self): """DTA: transitions with excluded source type.""" - self.a.set_reverse(False) - self.a.set_exclude(["trans1"]) + self.a.reverse = False + self.a.exclude = ["trans1"] paths = list(self.a.transitions("trans1")) self.assertEqual(0, len(paths)) def test_942_transitions_source_disconnected(self): """DTA: transitions with disconnected source type.""" - self.a.set_reverse(False) - self.a.set_exclude(["trans3"]) + self.a.reverse = False + self.a.exclude = ["trans3"] paths = list(self.a.transitions("trans5")) self.assertEqual(0, len(paths)) diff --git a/tests/infoflow.py b/tests/infoflow.py index 331a7b6..c6744f7 100644 --- a/tests/infoflow.py +++ b/tests/infoflow.py @@ -129,8 +129,8 @@ class InfoFlowAnalysisTest(mixins.ValidateRule, unittest.TestCase): def test_100_minimum_3(self): """Information flow analysis with minimum weight 3.""" - self.a.set_exclude(None) - self.a.set_min_weight(3) + self.a.exclude = None + self.a.min_weight = 3 self.a._build_subgraph() disconnected1 = self.p.lookup_type("disconnected1") @@ -166,8 +166,8 @@ class InfoFlowAnalysisTest(mixins.ValidateRule, unittest.TestCase): def test_200_minimum_8(self): """Information flow analysis with minimum weight 8.""" - self.a.set_exclude(None) - self.a.set_min_weight(8) + self.a.exclude = None + self.a.min_weight = 8 self.a._build_subgraph() disconnected1 = self.p.lookup_type("disconnected1") @@ -199,8 +199,8 @@ class InfoFlowAnalysisTest(mixins.ValidateRule, unittest.TestCase): def test_300_all_paths(self): """Information flow analysis: all paths output""" - self.a.set_exclude(None) - self.a.set_min_weight(1) + self.a.exclude = None + self.a.min_weight = 1 paths = list(self.a.all_paths("node1", "node4", 3)) self.assertEqual(1, len(paths)) @@ -226,8 +226,8 @@ class InfoFlowAnalysisTest(mixins.ValidateRule, unittest.TestCase): def test_301_all_shortest_paths(self): """Information flow analysis: all shortest paths output""" - self.a.set_exclude(None) - self.a.set_min_weight(1) + self.a.exclude = None + self.a.min_weight = 1 paths = list(self.a.all_shortest_paths("node1", "node4")) self.assertEqual(1, len(paths)) @@ -253,8 +253,8 @@ class InfoFlowAnalysisTest(mixins.ValidateRule, unittest.TestCase): def test_302_shortest_path(self): """Information flow analysis: shortest path output""" - self.a.set_exclude(None) - self.a.set_min_weight(1) + self.a.exclude = None + self.a.min_weight = 1 paths = list(self.a.shortest_path("node1", "node4")) self.assertEqual(1, len(paths)) @@ -280,8 +280,8 @@ class InfoFlowAnalysisTest(mixins.ValidateRule, unittest.TestCase): def test_303_infoflows_out(self): """Information flow analysis: flows out of a type""" - self.a.set_exclude(None) - self.a.set_min_weight(1) + self.a.exclude = None + self.a.min_weight = 1 for flow in self.a.infoflows("node6"): self.assertIsInstance(flow.source, Type) @@ -292,8 +292,8 @@ class InfoFlowAnalysisTest(mixins.ValidateRule, unittest.TestCase): def test_304_infoflows_in(self): """Information flow analysis: flows in to a type""" - self.a.set_exclude(None) - self.a.set_min_weight(1) + self.a.exclude = None + self.a.min_weight = 1 for flow in self.a.infoflows("node8", out=False): self.assertIsInstance(flow.source, Type) @@ -304,168 +304,176 @@ class InfoFlowAnalysisTest(mixins.ValidateRule, unittest.TestCase): def test_900_set_exclude_invalid_type(self): """Information flow analysis: set invalid excluded type.""" - self.assertRaises(InvalidType, self.a.set_exclude, ["node1", "invalid_type"]) + with self.assertRaises(InvalidType): + self.a.exclude = ["node1", "invalid_type"] def test_901_set_small_min_weight(self): """Information flow analysis: set too small weight.""" - self.assertRaises(ValueError, self.a.set_min_weight, 0) - self.assertRaises(ValueError, self.a.set_min_weight, -3) + + with self.assertRaises(ValueError): + self.a.min_weight = 0 + + with self.assertRaises(ValueError): + self.a.min_weight = -3 def test_902_set_large_min_weight(self): """Information flow analysis: set too big weight.""" - self.assertRaises(ValueError, self.a.set_min_weight, 11) - self.assertRaises(ValueError, self.a.set_min_weight, 50) + with self.assertRaises(ValueError): + self.a.min_weight = 11 + + with self.assertRaises(ValueError): + self.a.min_weight = 50 def test_910_all_paths_invalid_source(self): """Information flow analysis: all paths with invalid source type.""" - self.a.set_exclude(None) - self.a.set_min_weight(1) + self.a.exclude = None + self.a.min_weight = 1 with self.assertRaises(InvalidType): list(self.a.all_paths("invalid_type", "node1")) def test_911_all_paths_invalid_target(self): """Information flow analysis: all paths with invalid target type.""" - self.a.set_exclude(None) - self.a.set_min_weight(1) + self.a.exclude = None + self.a.min_weight = 1 with self.assertRaises(InvalidType): list(self.a.all_paths("node1", "invalid_type")) def test_912_all_paths_invalid_maxlen(self): """Information flow analysis: all paths with invalid max path length.""" - self.a.set_exclude(None) - self.a.set_min_weight(1) + self.a.exclude = None + self.a.min_weight = 1 with self.assertRaises(ValueError): list(self.a.all_paths("node1", "node2", maxlen=-2)) def test_913_all_paths_source_excluded(self): """Information flow analysis: all paths with excluded source type.""" - self.a.set_exclude(["node1"]) - self.a.set_min_weight(1) + self.a.exclude = ["node1"] + self.a.min_weight = 1 paths = list(self.a.all_paths("node1", "node2")) self.assertEqual(0, len(paths)) def test_914_all_paths_target_excluded(self): """Information flow analysis: all paths with excluded target type.""" - self.a.set_exclude(["node2"]) - self.a.set_min_weight(1) + self.a.exclude = ["node2"] + self.a.min_weight = 1 paths = list(self.a.all_paths("node1", "node2")) self.assertEqual(0, len(paths)) def test_915_all_paths_source_disconnected(self): """Information flow analysis: all paths with disconnected source type.""" - self.a.set_exclude(None) - self.a.set_min_weight(1) + self.a.exclude = None + self.a.min_weight = 1 paths = list(self.a.all_paths("disconnected1", "node2")) self.assertEqual(0, len(paths)) def test_916_all_paths_target_disconnected(self): """Information flow analysis: all paths with disconnected target type.""" - self.a.set_exclude(None) - self.a.set_min_weight(1) + self.a.exclude = None + self.a.min_weight = 1 paths = list(self.a.all_paths("node2", "disconnected1")) self.assertEqual(0, len(paths)) def test_920_shortest_path_invalid_source(self): """Information flow analysis: shortest path with invalid source type.""" - self.a.set_exclude(None) - self.a.set_min_weight(1) + self.a.exclude = None + self.a.min_weight = 1 with self.assertRaises(InvalidType): list(self.a.shortest_path("invalid_type", "node1")) def test_921_shortest_path_invalid_target(self): """Information flow analysis: shortest path with invalid target type.""" - self.a.set_exclude(None) - self.a.set_min_weight(1) + self.a.exclude = None + self.a.min_weight = 1 with self.assertRaises(InvalidType): list(self.a.shortest_path("node1", "invalid_type")) def test_922_shortest_path_source_excluded(self): """Information flow analysis: shortest path with excluded source type.""" - self.a.set_exclude(["node1"]) - self.a.set_min_weight(1) + self.a.exclude = ["node1"] + self.a.min_weight = 1 paths = list(self.a.shortest_path("node1", "node2")) self.assertEqual(0, len(paths)) def test_923_shortest_path_target_excluded(self): """Information flow analysis: shortest path with excluded target type.""" - self.a.set_exclude(["node2"]) - self.a.set_min_weight(1) + self.a.exclude = ["node2"] + self.a.min_weight = 1 paths = list(self.a.shortest_path("node1", "node2")) self.assertEqual(0, len(paths)) def test_924_shortest_path_source_disconnected(self): """Information flow analysis: shortest path with disconnected source type.""" - self.a.set_exclude(None) - self.a.set_min_weight(1) + self.a.exclude = None + self.a.min_weight = 1 paths = list(self.a.shortest_path("disconnected1", "node2")) self.assertEqual(0, len(paths)) def test_925_shortest_path_target_disconnected(self): """Information flow analysis: shortest path with disconnected target type.""" - self.a.set_exclude(None) - self.a.set_min_weight(1) + self.a.exclude = None + self.a.min_weight = 1 paths = list(self.a.shortest_path("node2", "disconnected1")) self.assertEqual(0, len(paths)) def test_930_all_shortest_paths_invalid_source(self): """Information flow analysis: all shortest paths with invalid source type.""" - self.a.set_exclude(None) - self.a.set_min_weight(1) + self.a.exclude = None + self.a.min_weight = 1 with self.assertRaises(InvalidType): list(self.a.all_shortest_paths("invalid_type", "node1")) def test_931_all_shortest_paths_invalid_target(self): """Information flow analysis: all shortest paths with invalid target type.""" - self.a.set_exclude(None) - self.a.set_min_weight(1) + self.a.exclude = None + self.a.min_weight = 1 with self.assertRaises(InvalidType): list(self.a.all_shortest_paths("node1", "invalid_type")) def test_932_all_shortest_paths_source_excluded(self): """Information flow analysis: all shortest paths with excluded source type.""" - self.a.set_exclude(["node1"]) - self.a.set_min_weight(1) + self.a.exclude = ["node1"] + self.a.min_weight = 1 paths = list(self.a.all_shortest_paths("node1", "node2")) self.assertEqual(0, len(paths)) def test_933_all_shortest_paths_target_excluded(self): """Information flow analysis: all shortest paths with excluded target type.""" - self.a.set_exclude(["node2"]) - self.a.set_min_weight(1) + self.a.exclude = ["node2"] + self.a.min_weight = 1 paths = list(self.a.all_shortest_paths("node1", "node2")) self.assertEqual(0, len(paths)) def test_934_all_shortest_paths_source_disconnected(self): """Information flow analysis: all shortest paths with disconnected source type.""" - self.a.set_exclude(None) - self.a.set_min_weight(1) + self.a.exclude = None + self.a.min_weight = 1 paths = list(self.a.all_shortest_paths("disconnected1", "node2")) self.assertEqual(0, len(paths)) def test_935_all_shortest_paths_target_disconnected(self): """Information flow analysis: all shortest paths with disconnected target type.""" - self.a.set_exclude(None) - self.a.set_min_weight(1) + self.a.exclude = None + self.a.min_weight = 1 paths = list(self.a.all_shortest_paths("node2", "disconnected1")) self.assertEqual(0, len(paths)) def test_940_infoflows_invalid_source(self): """Information flow analysis: infoflows with invalid source type.""" - self.a.set_exclude(None) - self.a.set_min_weight(1) + self.a.exclude = None + self.a.min_weight = 1 with self.assertRaises(InvalidType): list(self.a.infoflows("invalid_type")) def test_941_infoflows_source_excluded(self): """Information flow analysis: infoflows with excluded source type.""" - self.a.set_exclude(["node1"]) - self.a.set_min_weight(1) + self.a.exclude = ["node1"] + self.a.min_weight = 1 paths = list(self.a.infoflows("node1")) self.assertEqual(0, len(paths)) def test_942_infoflows_source_disconnected(self): """Information flow analysis: infoflows with disconnected source type.""" - self.a.set_exclude(["disconnected2"]) - self.a.set_min_weight(1) + self.a.exclude = ["disconnected2"] + self.a.min_weight = 1 paths = list(self.a.infoflows("disconnected1")) self.assertEqual(0, len(paths)) diff --git a/tests/mlsrulequery.py b/tests/mlsrulequery.py index 5fb6e68..556c3cb 100644 --- a/tests/mlsrulequery.py +++ b/tests/mlsrulequery.py @@ -18,6 +18,7 @@ import unittest from setools import SELinuxPolicy, MLSRuleQuery +from setools.policyrep.exception import InvalidMLSRuleType from . import mixins @@ -82,6 +83,7 @@ class MLSRuleQueryTest(mixins.ValidateRule, unittest.TestCase): self.assertEqual(len(r), 1) self.validate_rule(r[0], "range_transition", "test12s", "test12aFAIL", "infoflow", "s2") + @unittest.skip("Setting tclass to a string is no longer supported.") def test_020_class(self): """MLS rule query with exact object class match.""" q = MLSRuleQuery(self.p, tclass="infoflow7", tclass_regex=False) @@ -273,3 +275,8 @@ class MLSRuleQueryTest(mixins.ValidateRule, unittest.TestCase): self.assertEqual(len(r), 1) self.validate_rule(r[0], "range_transition", "test45", "test45", "infoflow", "s45:c1 - s45:c1.c3") + + def test_900_invalid_ruletype(self): + """MLS rule query with invalid rule type.""" + with self.assertRaises(InvalidMLSRuleType): + q = MLSRuleQuery(self.p, ruletype="type_transition") diff --git a/tests/rbacrulequery.conf b/tests/rbacrulequery.conf index 0db31c7..e8a6195 100644 --- a/tests/rbacrulequery.conf +++ b/tests/rbacrulequery.conf @@ -135,6 +135,18 @@ role test11t3; allow test11s test11t1; role_transition test11s system:infoflow test11t3; +# test 12 +# ruletype: unset +# source: unset +# target: test12t +# class: unset +# default: unset +role test12s; +type test12t; +role test12d; +allow test12s test12d; +role_transition test12s test12t:infoflow test12d; + # test 20 # ruletype: unset # source: unset diff --git a/tests/rbacrulequery.py b/tests/rbacrulequery.py index 4cd459c..a89d20a 100644 --- a/tests/rbacrulequery.py +++ b/tests/rbacrulequery.py @@ -90,6 +90,15 @@ class RBACRuleQueryTest(mixins.ValidateRule, unittest.TestCase): self.assertEqual(len(r), 1) self.validate_allow(r[0], "test11s", "test11t1") + def test_012_target_type(self): + """RBAC rule query with a type as target.""" + q = RBACRuleQuery(self.p, target="test12t") + + r = sorted(q.results()) + self.assertEqual(len(r), 1) + self.validate_rule(r[0], "role_transition", "test12s", "test12t", "infoflow", "test12d") + + @unittest.skip("Setting tclass to a string is no longer supported.") def test_020_class(self): """RBAC rule query with exact object class match.""" q = RBACRuleQuery(self.p, tclass="infoflow2", tclass_regex=False) @@ -146,4 +155,4 @@ class RBACRuleQueryTest(mixins.ValidateRule, unittest.TestCase): # this will have to be updated as number of # role allows change in the test policy - self.assertEqual(num, 8) + self.assertEqual(num, 9) diff --git a/tests/terulequery.py b/tests/terulequery.py index c7e0862..c22dfa9 100644 --- a/tests/terulequery.py +++ b/tests/terulequery.py @@ -117,6 +117,7 @@ class TERuleQueryTest(mixins.ValidateRule, unittest.TestCase): self.validate_rule(r[0], "allow", "test8a1", "test8a1", "infoflow", set(["hi_w"])) self.validate_rule(r[1], "allow", "test8a2", "test8a2", "infoflow", set(["low_r"])) + @unittest.skip("Setting tclass to a string is no longer supported.") def test_009_class(self): """TE rule query with exact object class match.""" q = TERuleQuery(self.p, tclass="infoflow2", tclass_regex=False) diff --git a/tests/typequery.py b/tests/typequery.py index 8f3fc6c..892c097 100644 --- a/tests/typequery.py +++ b/tests/typequery.py @@ -89,7 +89,7 @@ class TypeQueryTest(unittest.TestCase): def test_030_permissive(self): """Type query with permissive match""" - q = TypeQuery(self.p, match_permissive=True, permissive=True) + q = TypeQuery(self.p, permissive=True) types = sorted(str(t) for t in q.results()) self.assertListEqual(["test30"], types)