mirror of
https://github.com/SELinuxProject/setools
synced 2025-02-22 23:26:58 +00:00
parent
f5cace1420
commit
8f3a54eb37
217
setoolsgui/apol/boolquery.py
Normal file
217
setoolsgui/apol/boolquery.py
Normal file
@ -0,0 +1,217 @@
|
||||
# Copyright 2016, 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/>.
|
||||
#
|
||||
|
||||
import logging
|
||||
|
||||
from PyQt5.QtCore import pyqtSignal, Qt, QObject, QSortFilterProxyModel, QStringListModel, QThread
|
||||
from PyQt5.QtGui import QPalette, QTextCursor
|
||||
from PyQt5.QtWidgets import QCompleter, QHeaderView, QMessageBox, QProgressDialog, QScrollArea
|
||||
from setools import BoolQuery
|
||||
|
||||
from ..logtosignal import LogHandlerToSignal
|
||||
from ..models import SEToolsListModel, invert_list_selection
|
||||
from ..boolmodel import BooleanTableModel, boolean_detail
|
||||
from ..widget import SEToolsWidget
|
||||
|
||||
|
||||
class BoolQueryTab(SEToolsWidget, QScrollArea):
|
||||
|
||||
"""Bool browser and query tab."""
|
||||
|
||||
def __init__(self, parent, policy, perm_map):
|
||||
super(BoolQueryTab, self).__init__(parent)
|
||||
self.log = logging.getLogger(__name__)
|
||||
self.policy = policy
|
||||
self.query = BoolQuery(policy)
|
||||
self.setupUi()
|
||||
|
||||
def __del__(self):
|
||||
self.thread.quit()
|
||||
self.thread.wait(5000)
|
||||
logging.getLogger("setools.boolquery").removeHandler(self.handler)
|
||||
|
||||
def setupUi(self):
|
||||
self.load_ui("boolquery.ui")
|
||||
|
||||
# populate bool list
|
||||
self.bool_model = SEToolsListModel(self)
|
||||
self.bool_model.item_list = sorted(r for r in self.policy.bools())
|
||||
self.bools.setModel(self.bool_model)
|
||||
|
||||
# set up results
|
||||
self.table_results_model = BooleanTableModel(self)
|
||||
self.sort_proxy = QSortFilterProxyModel(self)
|
||||
self.sort_proxy.setSourceModel(self.table_results_model)
|
||||
self.table_results.setModel(self.sort_proxy)
|
||||
|
||||
# setup indications of errors on level/range
|
||||
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 = ResultsUpdater(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.boolquery").addHandler(self.handler)
|
||||
|
||||
# Ensure settings are consistent with the initial .ui state
|
||||
self.notes.setHidden(not self.notes_expander.isChecked())
|
||||
|
||||
# connect signals
|
||||
self.bools.doubleClicked.connect(self.get_detail)
|
||||
self.bools.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.buttonBox.clicked.connect(self.run)
|
||||
|
||||
#
|
||||
# Booleans browser
|
||||
#
|
||||
def get_detail(self):
|
||||
# .ui is set for single item selection.
|
||||
index = self.bools.selectedIndexes()[0]
|
||||
item = self.bool_model.data(index, Qt.UserRole)
|
||||
|
||||
self.log.debug("Generating detail window for {0}".format(item))
|
||||
boolean_detail(self, item)
|
||||
|
||||
#
|
||||
# Name criteria
|
||||
#
|
||||
def clear_name_error(self):
|
||||
self.name.setToolTip("Match the Boolean name.")
|
||||
self.name.setPalette(self.orig_palette)
|
||||
|
||||
def set_name(self):
|
||||
try:
|
||||
self.query.name = self.name.text()
|
||||
except Exception as ex:
|
||||
self.log.error("Boolean name error: {0}".format(ex))
|
||||
self.name.setToolTip("Error: " + str(ex))
|
||||
self.name.setPalette(self.error_palette)
|
||||
|
||||
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()
|
||||
|
||||
#
|
||||
# Results runner
|
||||
#
|
||||
|
||||
def run(self, button):
|
||||
# right now there is only one button.
|
||||
if self.default_any.isChecked():
|
||||
self.query.default = None
|
||||
else:
|
||||
self.query.default = self.default_true.isChecked()
|
||||
|
||||
# start processing
|
||||
self.busy.setLabelText("Processing query...")
|
||||
self.busy.show()
|
||||
self.raw_results.clear()
|
||||
self.thread.start()
|
||||
|
||||
def update_complete(self):
|
||||
# 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 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()
|
||||
|
||||
|
||||
class ResultsUpdater(QObject):
|
||||
|
||||
"""
|
||||
Thread for processing queries and updating result widgets.
|
||||
|
||||
Parameters:
|
||||
query The query object
|
||||
model The model for the results
|
||||
|
||||
Qt signals:
|
||||
finished The update has completed.
|
||||
raw_line (str) A string to be appended to the raw results.
|
||||
"""
|
||||
|
||||
finished = pyqtSignal()
|
||||
raw_line = pyqtSignal(str)
|
||||
|
||||
def __init__(self, query, model):
|
||||
super(ResultsUpdater, self).__init__()
|
||||
self.query = query
|
||||
self.log = logging.getLogger(__name__)
|
||||
self.table_results_model = model
|
||||
|
||||
def update(self):
|
||||
"""Run the query and update results."""
|
||||
self.table_results_model.beginResetModel()
|
||||
|
||||
results = []
|
||||
counter = 0
|
||||
|
||||
for counter, item in enumerate(self.query.results(), start=1):
|
||||
results.append(item)
|
||||
|
||||
self.raw_line.emit(item.statement())
|
||||
|
||||
if QThread.currentThread().isInterruptionRequested():
|
||||
break
|
||||
elif not counter % 10:
|
||||
# yield execution every 10 rules
|
||||
QThread.yieldCurrentThread()
|
||||
|
||||
self.table_results_model.resultlist = results
|
||||
self.table_results_model.endResetModel()
|
||||
|
||||
self.log.info("{0} Boolean(s) found.".format(counter))
|
||||
|
||||
self.finished.emit()
|
@ -27,6 +27,7 @@ from setools import PermissionMap, SELinuxPolicy
|
||||
from ..widget import SEToolsWidget
|
||||
from ..logtosignal import LogHandlerToSignal
|
||||
# Analysis tabs:
|
||||
from .boolquery import BoolQueryTab
|
||||
from .dta import DomainTransitionAnalysisTab
|
||||
from .infoflow import InfoFlowAnalysisTab
|
||||
from .mlsrulequery import MLSRuleQueryTab
|
||||
@ -232,7 +233,8 @@ class ChooseAnalysis(SEToolsWidget, QDialog):
|
||||
|
||||
_analysis_map = {"Domain Transition Analysis": DomainTransitionAnalysisTab,
|
||||
"Information Flow Analysis": InfoFlowAnalysisTab}
|
||||
_components_map = {"Roles": RoleQueryTab,
|
||||
_components_map = {"Booleans": BoolQueryTab,
|
||||
"Roles": RoleQueryTab,
|
||||
"Types": TypeQueryTab,
|
||||
"Users": UserQueryTab}
|
||||
_rule_map = {"RBAC Rules": RBACRuleQueryTab,
|
||||
|
80
setoolsgui/boolmodel.py
Normal file
80
setoolsgui/boolmodel.py
Normal file
@ -0,0 +1,80 @@
|
||||
# Copyright 2016, 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/>.
|
||||
#
|
||||
from collections import defaultdict
|
||||
|
||||
from PyQt5.QtCore import Qt, QAbstractTableModel, QModelIndex
|
||||
from PyQt5.QtGui import QPalette, QTextCursor
|
||||
|
||||
from .details import DetailsPopup
|
||||
|
||||
|
||||
def boolean_detail(parent, boolean):
|
||||
"""
|
||||
Create a dialog box for Booleanean details.
|
||||
|
||||
Parameters:
|
||||
parent The parent Qt Widget
|
||||
bool The boolean
|
||||
"""
|
||||
|
||||
detail = DetailsPopup(parent, "Boolean detail: {0}".format(boolean))
|
||||
|
||||
detail.append_header("Default State: {0}".format(boolean.state))
|
||||
|
||||
detail.show()
|
||||
|
||||
|
||||
class BooleanTableModel(QAbstractTableModel):
|
||||
|
||||
"""Table-based model for booleans."""
|
||||
|
||||
headers = defaultdict(None, {0: "Name", 1: "Default State"})
|
||||
|
||||
def __init__(self, parent):
|
||||
super(BooleanTableModel, self).__init__(parent)
|
||||
self.resultlist = []
|
||||
|
||||
def headerData(self, section, orientation, role):
|
||||
if role == Qt.DisplayRole and orientation == Qt.Horizontal:
|
||||
return self.headers[section]
|
||||
|
||||
def columnCount(self, parent=QModelIndex()):
|
||||
return 2
|
||||
|
||||
def rowCount(self, parent=QModelIndex()):
|
||||
if self.resultlist:
|
||||
return len(self.resultlist)
|
||||
else:
|
||||
return 0
|
||||
|
||||
def data(self, index, role):
|
||||
if self.resultlist:
|
||||
row = index.row()
|
||||
col = index.column()
|
||||
boolean = self.resultlist[row]
|
||||
|
||||
if role == Qt.DisplayRole:
|
||||
if col == 0:
|
||||
return str(boolean)
|
||||
elif col == 1:
|
||||
return str(boolean.state)
|
||||
|
||||
elif role == Qt.UserRole:
|
||||
# get the whole rule for boolean boolean
|
||||
return boolean
|
Loading…
Reference in New Issue
Block a user