diff --git a/setoolsgui/apol.py b/setoolsgui/apol.py
index 7827c2d0..6d2b8676 100644
--- a/setoolsgui/apol.py
+++ b/setoolsgui/apol.py
@@ -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,
diff --git a/setoolsgui/apol/commonquery.py b/setoolsgui/apol/commonquery.py
deleted file mode 100644
index e90ec543..00000000
--- a/setoolsgui/apol/commonquery.py
+++ /dev/null
@@ -1,217 +0,0 @@
-# Copyright 2016, Tresys Technology, LLC
-#
-# SPDX-License-Identifier: LGPL-2.1-only
-#
-#
-
-import logging
-from contextlib import suppress
-
-from PyQt5.QtCore import Qt, QSortFilterProxyModel, QStringListModel, QThread
-from PyQt5.QtGui import QPalette, QTextCursor
-from PyQt5.QtWidgets import QCompleter, QHeaderView, QMessageBox, QProgressDialog
-from setools import CommonQuery
-
-from ..logtosignal import LogHandlerToSignal
-from ..models import SEToolsListModel, invert_list_selection
-from ..commonmodel import CommonTableModel, common_detail
-from .analysistab import AnalysisSection, AnalysisTab
-from .exception import TabFieldError
-from .queryupdater import QueryResultsUpdater
-from .workspace import load_checkboxes, load_lineedits, load_listviews, load_textedits, \
- save_checkboxes, save_lineedits, save_listviews, save_textedits
-
-
-class CommonQueryTab(AnalysisTab):
-
- """Common browser and query tab."""
-
- section = AnalysisSection.Components
- tab_title = "Common Permission Sets"
- mlsonly = False
-
- def __init__(self, parent, policy, perm_map):
- super(CommonQueryTab, self).__init__(parent)
- self.log = logging.getLogger(__name__)
- self.policy = policy
- self.query = CommonQuery(policy)
- self.setupUi()
-
- def __del__(self):
- with suppress(RuntimeError):
- self.thread.quit()
- self.thread.wait(5000)
-
- logging.getLogger("setools.commonquery").removeHandler(self.handler)
-
- def setupUi(self):
- self.load_ui("apol/commonquery.ui")
-
- # populate commons list
- self.common_model = SEToolsListModel(self)
- self.common_model.item_list = sorted(c for c in self.policy.commons())
- self.commons.setModel(self.common_model)
-
- # populate perm list
- self.perms_model = SEToolsListModel(self)
- perms = set()
- for com in self.policy.commons():
- perms.update(com.perms)
- self.perms_model.item_list = sorted(perms)
- self.perms.setModel(self.perms_model)
-
- # set up results
- self.table_results_model = CommonTableModel(self)
- self.sort_proxy = QSortFilterProxyModel(self)
- self.sort_proxy.setSourceModel(self.table_results_model)
- self.table_results.setModel(self.sort_proxy)
- self.table_results.sortByColumn(0, Qt.AscendingOrder)
-
- # setup indications of errors
- self.errors = set()
- self.orig_palette = self.name.palette()
- self.error_palette = self.name.palette()
- self.error_palette.setColor(QPalette.Base, Qt.red)
- self.clear_name_error()
-
- # set up processing thread
- self.thread = QThread()
- self.worker = QueryResultsUpdater(self.query, self.table_results_model)
- self.worker.moveToThread(self.thread)
- self.worker.raw_line.connect(self.raw_results.appendPlainText)
- self.worker.finished.connect(self.update_complete)
- self.worker.finished.connect(self.thread.quit)
- self.thread.started.connect(self.worker.update)
-
- # create a "busy, please wait" dialog
- self.busy = QProgressDialog(self)
- self.busy.setModal(True)
- self.busy.setRange(0, 0)
- self.busy.setMinimumDuration(0)
- self.busy.canceled.connect(self.thread.requestInterruption)
- self.busy.reset()
-
- # update busy dialog from query INFO logs
- self.handler = LogHandlerToSignal()
- self.handler.message.connect(self.busy.setLabelText)
- logging.getLogger("setools.commonquery").addHandler(self.handler)
-
- # Ensure settings are consistent with the initial .ui state
- self.set_name_regex(self.name_regex.isChecked())
- self.notes.setHidden(not self.notes_expander.isChecked())
-
- # connect signals
- self.commons.doubleClicked.connect(self.get_detail)
- self.commons.get_detail.triggered.connect(self.get_detail)
- self.name.textEdited.connect(self.clear_name_error)
- self.name.editingFinished.connect(self.set_name)
- self.name_regex.toggled.connect(self.set_name_regex)
- self.perms.selectionModel().selectionChanged.connect(self.set_perms)
- self.invert_perms.clicked.connect(self.invert_perms_selection)
- self.buttonBox.clicked.connect(self.run)
-
- #
- # Class browser
- #
- def get_detail(self):
- # .ui is set for single item selection.
- index = self.commons.selectedIndexes()[0]
- item = self.common_model.data(index, Qt.UserRole)
-
- self.log.debug("Generating detail window for {0}".format(item))
- common_detail(self, item)
-
- #
- # Name criteria
- #
- def clear_name_error(self):
- self.clear_criteria_error(self.name, "Match the common name.")
-
- def set_name(self):
- try:
- self.query.name = self.name.text()
- except Exception as ex:
- self.log.error("Common name error: {0}".format(ex))
- self.set_criteria_error(self.name, ex)
-
- def set_name_regex(self, state):
- self.log.debug("Setting name_regex {0}".format(state))
- self.query.name_regex = state
- self.clear_name_error()
- self.set_name()
-
- #
- # Permissions criteria
- #
- def set_perms(self):
- selected_perms = []
- for index in self.perms.selectionModel().selectedIndexes():
- selected_perms.append(self.perms_model.data(index, Qt.UserRole))
-
- self.query.perms = selected_perms
-
- def invert_perms_selection(self):
- invert_list_selection(self.perms.selectionModel())
-
- #
- # Save/Load tab
- #
- def save(self):
- """Return a dictionary of settings."""
- if self.errors:
- raise TabFieldError("Field(s) are in error: {0}".
- format(" ".join(o.objectName() for o in self.errors)))
-
- settings = {}
- save_checkboxes(self, settings, ["criteria_expander", "notes_expander", "name_regex",
- "perms_equal"])
- save_lineedits(self, settings, ["name"])
- save_listviews(self, settings, ["perms"])
- save_textedits(self, settings, ["notes"])
- return settings
-
- def load(self, settings):
- load_checkboxes(self, settings, ["criteria_expander", "notes_expander", "name_regex",
- "perms_equal"])
- load_lineedits(self, settings, ["name"])
- load_listviews(self, settings, ["perms"])
- load_textedits(self, settings, ["notes"])
-
- #
- # Results runner
- #
- def run(self, button):
- # right now there is only one button.
- self.query.perms_equal = self.perms_equal.isChecked()
-
- # start processing
- self.busy.setLabelText("Processing query...")
- self.busy.show()
- self.raw_results.clear()
- self.thread.start()
-
- def update_complete(self, count):
- self.log.info("{0} common(s) found.".format(count))
-
- # update sizes/location of result displays
- if not self.busy.wasCanceled():
- self.busy.setLabelText("Resizing the result table's columns; GUI may be unresponsive")
- self.busy.repaint()
- self.table_results.resizeColumnsToContents()
- # If the permissions column width is too long, pull back
- # to a reasonable size
- header = self.table_results.horizontalHeader()
- if header.sectionSize(1) > 400:
- header.resizeSection(1, 400)
-
- if not self.busy.wasCanceled():
- self.busy.setLabelText("Resizing the result table's rows; GUI may be unresponsive")
- self.busy.repaint()
- self.table_results.resizeRowsToContents()
-
- if not self.busy.wasCanceled():
- self.busy.setLabelText("Moving the raw result to top; GUI may be unresponsive")
- self.busy.repaint()
- self.raw_results.moveCursor(QTextCursor.Start)
-
- self.busy.reset()
diff --git a/setoolsgui/apol/commonquery.ui b/setoolsgui/apol/commonquery.ui
deleted file mode 100644
index 9f893b71..00000000
--- a/setoolsgui/apol/commonquery.ui
+++ /dev/null
@@ -1,518 +0,0 @@
-
-
Search for common permission set by name.
") + + perms = criteria.PermissionCriteriaWidget("Permissions", self.query, "perms", + enable_equal=True) + perms.setToolTip("Search for common permission sets by permissions.") + perms.setWhatsThis("Search for common permission set by permissions.
") + + # 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) diff --git a/setoolsgui/widgets/criteria/__init__.py b/setoolsgui/widgets/criteria/__init__.py index f3dad5f9..17da499c 100644 --- a/setoolsgui/widgets/criteria/__init__.py +++ b/setoolsgui/widgets/criteria/__init__.py @@ -4,6 +4,7 @@ from .boolean import * from .comboenum import * +from .common import * from .constraintype import * from .context import * from .fsuseruletype import * diff --git a/setoolsgui/widgets/criteria/common.py b/setoolsgui/widgets/criteria/common.py new file mode 100644 index 00000000..7e9a2f1e --- /dev/null +++ b/setoolsgui/widgets/criteria/common.py @@ -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()) diff --git a/setoolsgui/widgets/details/__init__.py b/setoolsgui/widgets/details/__init__.py index 0ed9be27..454ffab6 100644 --- a/setoolsgui/widgets/details/__init__.py +++ b/setoolsgui/widgets/details/__init__.py @@ -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 * diff --git a/setoolsgui/widgets/details/common.py b/setoolsgui/widgets/details/common.py new file mode 100644 index 00000000..9afab36c --- /dev/null +++ b/setoolsgui/widgets/details/common.py @@ -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""" +{common}
+ +