Skip to content

Commit

Permalink
Add new CommonQueyTab implementation.
Browse files Browse the repository at this point in the history
Signed-off-by: Chris PeBenito <pebenito@ieee.org>
  • Loading branch information
pebenito committed Nov 30, 2023
1 parent c95fdfc commit 408a71d
Show file tree
Hide file tree
Showing 10 changed files with 274 additions and 0 deletions.
1 change: 1 addition & 0 deletions setoolsgui/apol.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
# will init the tab registry in widgets.tab for apol's analyses.
# pylint: disable=unused-import
from .widgets import (boolquery,
commonquery,
constraintquery,
fsusequery,
genfsconquery,
Expand Down
79 changes: 79 additions & 0 deletions setoolsgui/widgets/commonquery.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# SPDX-License-Identifier: LGPL-2.1-only

from PyQt6 import QtWidgets
import setools

from . import criteria, models, tab

__all__ = ("CommonQueryTab",)


class CommonQueryTab(tab.TableResultTabWidget):

"""A common permission set query."""

section = tab.AnalysisSection.Components
tab_title = "Common Permision Sets"
mlsonly = False

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

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

self.setWhatsThis("<b>Search common permission sets in an SELinux policy.</b>")

#
# Set up criteria widgets
#
name = criteria.CommonName("Name", self.query, "name", enable_regex=True,
parent=self.criteria_frame)
name.setToolTip("Search for common permission sets by name.")
name.setWhatsThis("<p>Search for common permission set by name.</p>")

perms = criteria.PermissionCriteriaWidget("Permissions", self.query, "perms",
enable_equal=True)
perms.setToolTip("Search for common permission sets by permissions.")
perms.setWhatsThis("<p>Search for common permission set by permissions.</p>")

# Add widgets to layout
self.criteria_frame_layout.addWidget(name, 0, 0, 1, 1)
self.criteria_frame_layout.addWidget(perms, 0, 1, 1, 1)
self.criteria_frame_layout.addWidget(self.buttonBox, 1, 0, 1, 2)

# Save widget references
self.criteria = (name, perms)

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

#
# Set up browser
#
self.browser.setModel(models.CommonTable(self.browser,
data=sorted(self.query.policy.commons())))


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 = CommonQueryTab(setools.SELinuxPolicy(), mw)
mw.setCentralWidget(widget)
mw.resize(1280, 1024)
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 .common import *
from .constraintype import *
from .context import *
from .fsuseruletype import *
Expand Down
59 changes: 59 additions & 0 deletions setoolsgui/widgets/criteria/common.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# SPDX-License-Identifier: LGPL-2.1-only

from PyQt6 import QtCore, QtWidgets
import setools

from .criteria import OptionsPlacement
from .name import NameCriteriaWidget

# Regex for exact matches to roles
VALIDATE_EXACT = r"[A-Za-z0-9._-]*"

__all__ = ("CommonName",)


class CommonName(NameCriteriaWidget):

"""
Widget providing a QLineEdit that saves the input to the attributes
of the specified query. This supports inputs of common names.
"""

indirect_toggled = QtCore.pyqtSignal(bool)

def __init__(self, title: str, query: setools.PolicyQuery, attrname: str, /, *,
parent: QtWidgets.QWidget | None = None,
options_placement: OptionsPlacement = OptionsPlacement.RIGHT,
required: bool = False, enable_regex: bool = True):

# Create completion list
completion = list[str](r.name for r in query.policy.commons())

super().__init__(title, query, attrname, completion, VALIDATE_EXACT,
enable_regex=enable_regex, required=required, parent=parent,
options_placement=options_placement)


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.CommonQuery(setools.SELinuxPolicy())

app = QtWidgets.QApplication(sys.argv)
mw = QtWidgets.QMainWindow()
widget = CommonName("Test Common", q, "name", parent=mw)
widget.setToolTip("test tooltip")
widget.setWhatsThis("test whats this")
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()
sys.exit(app.exec())
1 change: 1 addition & 0 deletions setoolsgui/widgets/details/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# SPDX-License-Identifier: LGPL-2.1-only

from .boolean import *
from .common import *
from .context import *
from .objclass import *
from .role import *
Expand Down
44 changes: 44 additions & 0 deletions setoolsgui/widgets/details/common.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# SPDX-License-Identifier: LGPL-2.1-only
from PyQt6 import QtGui, QtWidgets
import setools

from . import util

__all__ = ('common_detail', 'common_detail_action', 'common_tooltip')


def common_detail(common: setools.Common, parent: QtWidgets.QWidget | None = None) -> None:
"""Display a dialog with common details."""

util.display_object_details(
f"{common} Details",
f"""
<h1>Common Name</h1>
<p>{common}<p>
<h2>Permissions ({len(common.perms)})</h2>
<ul>
{"".join(f"<li>{p}</li>" for p in sorted(common.perms))}
</ul>
""",
parent)


def common_detail_action(common: setools.Common,
parent: QtWidgets.QWidget | None = None) -> QtGui.QAction:
"""Return a QAction that, when triggered, opens a common detail popup."""
a = QtGui.QAction(f"Properties of {common}")
a.triggered.connect(lambda x: common_detail(common, parent))
return a


def common_tooltip(common: setools.Common) -> str:
"""Return tooltip text for this common."""
nperms = len(common.perms)
if nperms == 0:
return f"{common} is a common permission set with no permissions defined."
elif nperms > 5:
return f"{common} is a common permission set with {nperms} permissions defined."
else:
return f"{common} is a common permission set with permissions: " \
f"{', '.join(common.perms)}"
8 changes: 8 additions & 0 deletions setoolsgui/widgets/models/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
from PyQt6 import QtCore
import setools

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

__all__ = ("CommonTable",)

Expand All @@ -33,4 +35,10 @@ def data(self, index: QtCore.QModelIndex, role: int = QtCore.Qt.ItemDataRole.Dis
case 1:
return ", ".join(sorted(item.perms))

case modelroles.ContextMenuRole:
return (details.common_detail_action(item), )

case QtCore.Qt.ItemDataRole.ToolTipRole:
return details.common_tooltip(item)

return super().data(index, role)
1 change: 1 addition & 0 deletions tests-gui/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ def mock_policy() -> Mock:
policy = Mock(setools.SELinuxPolicy)
policy.bools.return_value = (foo_bool, bar_bool)
policy.classes.return_value = (foo_class, bar_class)
policy.commons.return_value = (common,)
policy.roles.return_value = (foo_r, bar_r)
policy.types.return_value = (foo_t, bar_t)
policy.typeattributes.return_value = (fooattr, barattr)
Expand Down
24 changes: 24 additions & 0 deletions tests-gui/widgets/criteria/test_common.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# SPDX-License-Identifier: GPL-2.0-only
from PyQt6 import QtCore
import pytest
from pytestqt.qtbot import QtBot

from setoolsgui.widgets import criteria


@pytest.fixture
def widget(mock_query, request: pytest.FixtureRequest, qtbot: QtBot) -> criteria.CommonName:
"""Pytest fixture to set up the widget."""
marker = request.node.get_closest_marker("obj_args")
kwargs = marker.kwargs if marker else {}
w = criteria.CommonName(request.node.name, mock_query, "name", **kwargs)
qtbot.addWidget(w)
w.show()
return w


def test_base_settings(widget: criteria.CommonName, mock_query) -> None:
"""Test base properties of widget."""
model = widget.criteria.completer().model()
assert isinstance(model, QtCore.QStringListModel)
assert sorted(c.name for c in mock_query.policy.commons()) == model.stringList()
56 changes: 56 additions & 0 deletions tests-gui/widgets/test_commonquery.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# SPDX-License-Identifier: GPL-2.0-only
import typing

from PyQt6 import QtWidgets
import pytest
from pytestqt.qtbot import QtBot

import setools
from setoolsgui.widgets.commonquery import CommonQueryTab


@pytest.fixture
def widget(mock_policy, request: pytest.FixtureRequest, qtbot: QtBot) -> CommonQueryTab:
"""Pytest fixture to set up the widget."""
marker = request.node.get_closest_marker("obj_args")
kwargs = marker.kwargs if marker else {}
w = CommonQueryTab(mock_policy, None, **kwargs)
qtbot.addWidget(w)
w.show()
return w


def test_docs(widget: CommonQueryTab) -> None:
"""Check that docs are provided for the 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(widget: CommonQueryTab) -> None:
"""Test the layout of the criteria frame."""
name, perms = widget.criteria

assert widget.criteria_frame_layout.columnCount() == 2
assert widget.criteria_frame_layout.rowCount() == 2
assert widget.criteria_frame_layout.itemAtPosition(0, 0).widget() == name
assert widget.criteria_frame_layout.itemAtPosition(0, 1).widget() == perms
assert widget.criteria_frame_layout.itemAtPosition(1, 0).widget() == widget.buttonBox
assert widget.criteria_frame_layout.itemAtPosition(1, 1).widget() == widget.buttonBox


def test_criteria_mapping(widget: CommonQueryTab) -> None:
"""Test that widgets save to the correct query fields."""
name, state = widget.criteria

assert isinstance(widget.query, setools.CommonQuery)
assert name.attrname == "name"
assert state.attrname == "perms"

0 comments on commit 408a71d

Please sign in to comment.