Skip to content

Commit

Permalink
Add new ConstraintQueryTab implementation.
Browse files Browse the repository at this point in the history
Signed-off-by: Chris PeBenito <chpebeni@linux.microsoft.com>
  • Loading branch information
pebenito committed Nov 15, 2023
1 parent af42f0b commit 6984f2b
Show file tree
Hide file tree
Showing 6 changed files with 336 additions and 4 deletions.
4 changes: 3 additions & 1 deletion setoolsgui/apol.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@

# Supported analyses. These are not directly used here, but
# will init the tab registry in widgets.tab for apol's analyses.
from .widgets import (fsusequery,
# pylint: disable=unused-import
from .widgets import (constraintquery,
fsusequery,
genfsconquery,
ibendportconquery,
ibpkeyconquery,
Expand Down
152 changes: 152 additions & 0 deletions setoolsgui/widgets/constraintquery.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
# SPDX-License-Identifier: LGPL-2.1-only

from PyQt6 import QtWidgets
import setools

from . import criteria, models, tab

__all__ = ("ConstraintQueryTab",)


class ConstraintQueryTab(tab.TableResultTabWidget):

"""A constraint query."""

section = tab.AnalysisSection.Rules
tab_title = "Constraints"
mlsonly = False

def __init__(self, policy: "setools.SELinuxPolicy", _, /, *,
parent: QtWidgets.QWidget | None = None) -> None:

super().__init__(setools.ConstraintQuery(policy), None, enable_criteria=True,
parent=parent)

self.setWhatsThis("<b>Search constraints in a SELinux policy.</b>")

#
# Set up criteria widgets
#
rt = criteria.ConstrainType("Rule Type", self.query, parent=self.criteria_frame)
rt.setToolTip("The rule types for constraint matching.")
rt.setWhatsThis(
"""
<p><b>Select rule types for constraint matching.</b></p>
<p>If a rule's has a one of the selected types, it will be returned.</p>
""")

user = criteria.UserNameWidget("User In Expression",
self.query,
"user",
enable_regex=True,
parent=self.criteria_frame)
user.setToolTip("Search for a user in the expression.")
user.setWhatsThis(
"""
<p><b>Search for users in a constraint expression..</b></p>
<p>If a constraint's expression has this user in its expression,
it will be returned.</p>
""")

role = criteria.RoleNameWidget("Role In Expression",
self.query,
"role",
enable_regex=True,
parent=self.criteria_frame)
role.setToolTip("Search for a role in the expression.")
role.setWhatsThis(
"""
<p><b>Search for roles in a constraint expression..</b></p>
<p>If a constraint's expression has this role in its expression,
it will be returned.</p>
""")

type_ = criteria.TypeOrAttrNameWidget("Type In Expression",
self.query,
"type_",
mode=criteria.TypeOrAttrNameWidget.Mode.type_only,
enable_regex=True,
enable_indirect=False,
parent=self.criteria_frame)
type_.setToolTip("Search for a type in the expression.")
type_.setWhatsThis(
"""
<p><b>Search for types in a constraint expression..</b></p>
<p>If a constraint's expression has this type in its expression,
it will be returned.</p>
""")

tclass = criteria.ObjClassCriteriaWidget("Object Class",
self.query,
"tclass",
parent=self.criteria_frame)
tclass.setToolTip("The object class(es) for constraint matching.")
tclass.setWhatsThis(
"""
<p><b>Select object classes for constraint matching.</b></p>
<p>A rule will be returned if its object class is one of the selected
classes</p>
""")

perms = criteria.PermissionCriteriaWidget("Permission Set",
self.query,
"perms",
enable_equal=True,
enable_subset=True,
parent=self.criteria_frame)
perms.setToolTip("The permission(s) for constraint matching.")
perms.setWhatsThis(
"""
<p><b>Select permissions for constraint matching.</b></p>
<p>Available permissions are dependent on the selected object
classes. If multiple classes are selected, only permissions
available in all of the classes are available.</p>
""")

# Connect signals
tclass.selectionChanged.connect(perms.set_classes)

# Add widgets to layout
self.criteria_frame_layout.addWidget(rt, 0, 0, 1, 1)
self.criteria_frame_layout.addWidget(user, 0, 1, 1, 1)
self.criteria_frame_layout.addWidget(role, 1, 0, 1, 1)
self.criteria_frame_layout.addWidget(type_, 1, 1, 1, 1)
self.criteria_frame_layout.addWidget(tclass, 2, 0, 1, 1)
self.criteria_frame_layout.addWidget(perms, 2, 1, 1, 1)
self.criteria_frame_layout.addWidget(self.buttonBox, 3, 0, 1, 2)

# Save widget references
self.criteria = (rt, user, role, type_, tclass, perms)

# Set result table's model
self.table_results_model = models.ConstraintTable(self.table_results)


if __name__ == '__main__':
import sys
import warnings
import pprint
import logging

logging.basicConfig(level=logging.DEBUG,
format='%(asctime)s|%(levelname)s|%(name)s|%(message)s')
warnings.simplefilter("default")

app = QtWidgets.QApplication(sys.argv)
mw = QtWidgets.QMainWindow()
widget = ConstraintQueryTab(setools.SELinuxPolicy(), mw)
mw.setCentralWidget(widget)
mw.resize(widget.size())
whatsthis = QtWidgets.QWhatsThis.createAction(mw)
mw.menuBar().addAction(whatsthis) # type: ignore[union-attr]
mw.setStatusBar(QtWidgets.QStatusBar(mw))
mw.show()
rc = app.exec()
pprint.pprint(widget.save())
sys.exit(rc)
1 change: 1 addition & 0 deletions setoolsgui/widgets/criteria/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

from .boolean import *
from .comboenum import *
from .constraintype import *
from .context import *
from .fsuseruletype import *
from .infiniband import *
Expand Down
61 changes: 61 additions & 0 deletions setoolsgui/widgets/criteria/constraintype.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# SPDX-License-Identifier: LGPL-2.1-only

from PyQt6 import QtWidgets
import setools

from .checkboxset import CheckboxSetCriteriaWidget

DEFAULT_CHECKED = ("constrain",)

__all__ = ('ConstrainType',)


class ConstrainType(CheckboxSetCriteriaWidget):

"""
Criteria selection widget presenting type enforcement rule types as a series
of checkboxes. The selected checkboxes are then merged into a single Python
list consisting of object names (constraint types) and stored in the query's
specified attribute.
"""

def __init__(self, title: str, query: setools.PolicyQuery, attrname: str = "ruletype",
parent: QtWidgets.QWidget | None = None) -> None:

super().__init__(title, query, attrname, (rt.name for rt in setools.ConstraintRuletype),
num_cols=2, parent=parent)

for name, widget in self.criteria.items():
widget.setChecked(name in DEFAULT_CHECKED)
widget.setToolTip(f"Match {name} rules.")
widget.setWhatsThis(
f"""
<p><b>Match {name} rules</b></p>
<p>If a rule has the {name} rule type, it will be returned.</p>
""")


if __name__ == '__main__':
import sys
import logging
import warnings

logging.basicConfig(level=logging.DEBUG,
format='%(asctime)s|%(levelname)s|%(name)s|%(message)s')
warnings.simplefilter("default")

q = setools.ConstraintQuery(setools.SELinuxPolicy())

app = QtWidgets.QApplication(sys.argv)
mw = QtWidgets.QMainWindow()
w = ConstrainType("Test constrain ruletypes", q, parent=mw)
w.setToolTip("test tooltip")
w.setWhatsThis("test whats this")
mw.setCentralWidget(w)
mw.resize(w.size())
whatsthis = QtWidgets.QWhatsThis.createAction(mw)
mw.menuBar().addAction(whatsthis) # type: ignore[union-attr]
mw.show()
rc = app.exec()
sys.exit(rc)
54 changes: 51 additions & 3 deletions setoolsgui/widgets/models/constraint.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,20 @@
#
# SPDX-License-Identifier: LGPL-2.1-only
#
#

import typing

from PyQt6 import QtCore
import setools

from .. import details
from . import modelroles
from .table import SEToolsTableModel

HAS_PERMS: typing.Final[tuple[setools.ConstraintRuletype, ...]] = (
setools.ConstraintRuletype.constrain,
setools.ConstraintRuletype.mlsconstrain)

__all__ = ("ConstraintTable",)


Expand All @@ -33,12 +41,52 @@ def data(self, index: QtCore.QModelIndex, role: int = QtCore.Qt.ItemDataRole.Dis
case 1:
return rule.tclass.name
case 2:
if rule.ruletype in (setools.ConstraintRuletype.constrain,
setools.ConstraintRuletype.mlsconstrain):
if rule.ruletype in HAS_PERMS:
return ", ".join(sorted(rule.perms))
else:
return None
case 3:
return str(rule.expression)

case modelroles.ContextMenuRole:
if col == 2:
return details.objclass_detail_action(rule.tclass)

case QtCore.Qt.ItemDataRole.WhatsThisRole:
match col:
case 0:
column_whatsthis = \
"""
<p>This is the type of constraint.</p>
"""
case 1:
column_whatsthis = \
"""
<p>This is the object class of the constraint.</p>
"""
case 2:
if rule.ruletype in HAS_PERMS:
column_whatsthis = \
"""
<p>These are the permissions of the constraint.</p>
"""
else:
column_whatsthis = f"This column does not apply to {rule.ruletype}."
case 3:
column_whatsthis = \
"""
<p>This expression of the constraint.</p>
"""
case _:
column_whatsthis = ""

return \
f"""
<b><p>Table Representation of Constraints</p></b>
<p>Each part of the rule is represented as a column in the table.</p>
{column_whatsthis}
"""

return super().data(index, role)
68 changes: 68 additions & 0 deletions tests-gui/widgets/test_constraintquery.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# SPDX-License-Identifier: GPL-2.0-only
import typing

from PyQt6 import QtWidgets
from pytestqt.qtbot import QtBot

import setools
from setoolsgui.widgets.constraintquery import ConstraintQueryTab
from setoolsgui.widgets import models

from .criteria.util import build_mock_policy


def test_docs(qtbot: QtBot) -> None:
"""Check that docs are provided for the widget."""
mock_policy = build_mock_policy()
widget = ConstraintQueryTab(mock_policy, None)
qtbot.addWidget(widget)

assert widget.whatsThis()
assert widget.table_results.whatsThis()
assert widget.raw_results.whatsThis()

for w in widget.criteria:
assert w.toolTip()
assert w.whatsThis()

results = typing.cast(QtWidgets.QTabWidget, widget.results)
for index in range(results.count()):
assert results.tabWhatsThis(index)


def test_layout(qtbot: QtBot) -> None:
"""Test the layout of the criteria frame."""
mock_policy = build_mock_policy()
widget = ConstraintQueryTab(mock_policy, None)
qtbot.addWidget(widget)

rt, user, role, type_, tclass, perms = widget.criteria

assert widget.criteria_frame_layout.columnCount() == 2
assert widget.criteria_frame_layout.rowCount() == 4
assert widget.criteria_frame_layout.itemAtPosition(0, 0).widget() == rt
assert widget.criteria_frame_layout.itemAtPosition(0, 1).widget() == user
assert widget.criteria_frame_layout.itemAtPosition(1, 0).widget() == role
assert widget.criteria_frame_layout.itemAtPosition(1, 1).widget() == type_
assert widget.criteria_frame_layout.itemAtPosition(2, 0).widget() == tclass
assert widget.criteria_frame_layout.itemAtPosition(2, 1).widget() == perms
assert widget.criteria_frame_layout.itemAtPosition(3, 0).widget() == widget.buttonBox
assert widget.criteria_frame_layout.itemAtPosition(3, 1).widget() == widget.buttonBox


def test_criteria_mapping(qtbot: QtBot) -> None:
"""Test that widgets save to the correct query fields."""
mock_policy = build_mock_policy()
widget = ConstraintQueryTab(mock_policy, None)
qtbot.addWidget(widget)

rt, user, role, type_, tclass, perms = widget.criteria

assert isinstance(widget.query, setools.ConstraintQuery)
assert isinstance(widget.table_results_model, models.ConstraintTable)
assert rt.attrname == "ruletype"
assert user.attrname == "user"
assert role.attrname == "role"
assert type_.attrname == "type_"
assert tclass.attrname == "tclass"
assert perms.attrname == "perms"

0 comments on commit 6984f2b

Please sign in to comment.