mirror of
https://github.com/SELinuxProject/setools
synced 2025-04-10 19:41:23 +00:00
apol: implement browser for domain transition and infoflow analyses.
Closes #100
This commit is contained in:
parent
fa32092a3c
commit
6b00b01e10
63
data/dta.ui
63
data/dta.ui
@ -464,6 +464,16 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item row="0" column="4">
|
||||||
|
<widget class="QCheckBox" name="notes_expander">
|
||||||
|
<property name="toolTip">
|
||||||
|
<string>Show or hide the notes field (no data is lost)</string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Notes</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
<item row="2" column="0" colspan="6">
|
<item row="2" column="0" colspan="6">
|
||||||
<widget class="QTabWidget" name="results_frame">
|
<widget class="QTabWidget" name="results_frame">
|
||||||
<property name="sizePolicy">
|
<property name="sizePolicy">
|
||||||
@ -472,6 +482,9 @@
|
|||||||
<verstretch>1</verstretch>
|
<verstretch>1</verstretch>
|
||||||
</sizepolicy>
|
</sizepolicy>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="currentIndex">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
<widget class="QWidget" name="results_framePage2">
|
<widget class="QWidget" name="results_framePage2">
|
||||||
<property name="sizePolicy">
|
<property name="sizePolicy">
|
||||||
<sizepolicy hsizetype="Preferred" vsizetype="MinimumExpanding">
|
<sizepolicy hsizetype="Preferred" vsizetype="MinimumExpanding">
|
||||||
@ -530,16 +543,46 @@
|
|||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</widget>
|
<widget class="QWidget" name="browser_tab">
|
||||||
</item>
|
<attribute name="title">
|
||||||
<item row="0" column="4">
|
<string>Browser</string>
|
||||||
<widget class="QCheckBox" name="notes_expander">
|
</attribute>
|
||||||
<property name="toolTip">
|
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||||
<string>Show or hide the notes field (no data is lost)</string>
|
<item>
|
||||||
</property>
|
<widget class="QSplitter" name="splitter">
|
||||||
<property name="text">
|
<property name="orientation">
|
||||||
<string>Notes</string>
|
<enum>Qt::Horizontal</enum>
|
||||||
</property>
|
</property>
|
||||||
|
<widget class="QTreeWidget" name="browser">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||||
|
<horstretch>1</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<column>
|
||||||
|
<property name="text">
|
||||||
|
<string>Type</string>
|
||||||
|
</property>
|
||||||
|
</column>
|
||||||
|
</widget>
|
||||||
|
<widget class="QPlainTextEdit" name="browser_details">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||||
|
<horstretch>4</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<family>Monospace</family>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
|
@ -503,6 +503,49 @@
|
|||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
|
<widget class="QWidget" name="browser_tab">
|
||||||
|
<property name="enabled">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<attribute name="title">
|
||||||
|
<string>Browser</string>
|
||||||
|
</attribute>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QSplitter" name="splitter">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<widget class="QTreeWidget" name="browser">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||||
|
<horstretch>1</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<column>
|
||||||
|
<property name="text">
|
||||||
|
<string>Type</string>
|
||||||
|
</property>
|
||||||
|
</column>
|
||||||
|
</widget>
|
||||||
|
<widget class="QPlainTextEdit" name="browser_details">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||||
|
<horstretch>4</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<family>Monospace</family>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="0" column="3">
|
<item row="0" column="3">
|
||||||
|
@ -19,9 +19,10 @@
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from PyQt5.QtCore import pyqtSignal, Qt, QObject, QStringListModel, QThread
|
from PyQt5.QtCore import pyqtSignal, Qt, QStringListModel, QThread
|
||||||
from PyQt5.QtGui import QPalette, QTextCursor
|
from PyQt5.QtGui import QPalette, QTextCursor
|
||||||
from PyQt5.QtWidgets import QCompleter, QHeaderView, QMessageBox, QProgressDialog, QScrollArea
|
from PyQt5.QtWidgets import QCompleter, QHeaderView, QMessageBox, QProgressDialog, QScrollArea, \
|
||||||
|
QTreeWidgetItem
|
||||||
from setools import DomainTransitionAnalysis
|
from setools import DomainTransitionAnalysis
|
||||||
|
|
||||||
from ..logtosignal import LogHandlerToSignal
|
from ..logtosignal import LogHandlerToSignal
|
||||||
@ -68,13 +69,14 @@ class DomainTransitionAnalysisTab(SEToolsWidget, QScrollArea):
|
|||||||
self.clear_target_error()
|
self.clear_target_error()
|
||||||
|
|
||||||
# set up processing thread
|
# set up processing thread
|
||||||
self.thread = QThread()
|
self.thread = ResultsUpdater(self.query)
|
||||||
self.worker = ResultsUpdater(self.query)
|
self.thread.raw_line.connect(self.raw_results.appendPlainText)
|
||||||
self.worker.moveToThread(self.thread)
|
self.thread.finished.connect(self.update_complete)
|
||||||
self.worker.raw_line.connect(self.raw_results.appendPlainText)
|
self.thread.trans.connect(self.reset_browser)
|
||||||
self.worker.finished.connect(self.update_complete)
|
|
||||||
self.worker.finished.connect(self.thread.quit)
|
# set up browser thread
|
||||||
self.thread.started.connect(self.worker.update)
|
self.browser_thread = BrowserUpdater(self.query)
|
||||||
|
self.browser_thread.trans.connect(self.add_browser_children)
|
||||||
|
|
||||||
# create a "busy, please wait" dialog
|
# create a "busy, please wait" dialog
|
||||||
self.busy = QProgressDialog(self)
|
self.busy = QProgressDialog(self)
|
||||||
@ -95,6 +97,7 @@ class DomainTransitionAnalysisTab(SEToolsWidget, QScrollArea):
|
|||||||
self.target.setEnabled(not self.flows_out.isChecked())
|
self.target.setEnabled(not self.flows_out.isChecked())
|
||||||
self.criteria_frame.setHidden(not self.criteria_expander.isChecked())
|
self.criteria_frame.setHidden(not self.criteria_expander.isChecked())
|
||||||
self.notes.setHidden(not self.notes_expander.isChecked())
|
self.notes.setHidden(not self.notes_expander.isChecked())
|
||||||
|
self.browser_tab.setEnabled(self.flows_in.isChecked() or self.flows_out.isChecked())
|
||||||
|
|
||||||
# connect signals
|
# connect signals
|
||||||
self.buttonBox.clicked.connect(self.run)
|
self.buttonBox.clicked.connect(self.run)
|
||||||
@ -107,6 +110,7 @@ class DomainTransitionAnalysisTab(SEToolsWidget, QScrollArea):
|
|||||||
self.flows_out.toggled.connect(self.flows_out_toggled)
|
self.flows_out.toggled.connect(self.flows_out_toggled)
|
||||||
self.reverse.stateChanged.connect(self.reverse_toggled)
|
self.reverse.stateChanged.connect(self.reverse_toggled)
|
||||||
self.exclude_types.clicked.connect(self.choose_excluded_types)
|
self.exclude_types.clicked.connect(self.choose_excluded_types)
|
||||||
|
self.browser.currentItemChanged.connect(self.browser_item_selected)
|
||||||
|
|
||||||
#
|
#
|
||||||
# Analysis mode
|
# Analysis mode
|
||||||
@ -121,6 +125,8 @@ class DomainTransitionAnalysisTab(SEToolsWidget, QScrollArea):
|
|||||||
self.clear_target_error()
|
self.clear_target_error()
|
||||||
self.source.setEnabled(not value)
|
self.source.setEnabled(not value)
|
||||||
self.reverse.setEnabled(not value)
|
self.reverse.setEnabled(not value)
|
||||||
|
self.limit_paths.setEnabled(not value)
|
||||||
|
self.browser_tab.setEnabled(value)
|
||||||
|
|
||||||
if value:
|
if value:
|
||||||
self.reverse_old = self.reverse.isChecked()
|
self.reverse_old = self.reverse.isChecked()
|
||||||
@ -133,6 +139,8 @@ class DomainTransitionAnalysisTab(SEToolsWidget, QScrollArea):
|
|||||||
self.clear_target_error()
|
self.clear_target_error()
|
||||||
self.target.setEnabled(not value)
|
self.target.setEnabled(not value)
|
||||||
self.reverse.setEnabled(not value)
|
self.reverse.setEnabled(not value)
|
||||||
|
self.limit_paths.setEnabled(not value)
|
||||||
|
self.browser_tab.setEnabled(value)
|
||||||
|
|
||||||
if value:
|
if value:
|
||||||
self.reverse_old = self.reverse.isChecked()
|
self.reverse_old = self.reverse.isChecked()
|
||||||
@ -196,10 +204,91 @@ class DomainTransitionAnalysisTab(SEToolsWidget, QScrollArea):
|
|||||||
def reverse_toggled(self, value):
|
def reverse_toggled(self, value):
|
||||||
self.query.reverse = value
|
self.query.reverse = value
|
||||||
|
|
||||||
|
#
|
||||||
|
# Infoflow browser
|
||||||
|
#
|
||||||
|
def _new_browser_item(self, type_, parent, rules=None, children=None):
|
||||||
|
# build main item
|
||||||
|
item = QTreeWidgetItem(parent if parent else self.browser)
|
||||||
|
item.setText(0, str(type_))
|
||||||
|
item.type_ = type_
|
||||||
|
item.children = children if children else []
|
||||||
|
item.rules = rules if rules else []
|
||||||
|
item.child_populated = children is not None
|
||||||
|
|
||||||
|
# add child items
|
||||||
|
for child_type, child_rules in item.children:
|
||||||
|
child_item = self._new_browser_item(child_type, item, rules=child_rules)
|
||||||
|
item.addChild(child_item)
|
||||||
|
|
||||||
|
item.setExpanded(children is not None)
|
||||||
|
|
||||||
|
self.log.debug("Built item for {0} with {1} children and {2} rules".format(
|
||||||
|
type_, len(item.children), len(item.rules)))
|
||||||
|
|
||||||
|
return item
|
||||||
|
|
||||||
|
def reset_browser(self, root_type, out, children):
|
||||||
|
self.log.debug("Resetting browser.")
|
||||||
|
|
||||||
|
# clear results
|
||||||
|
self.browser.clear()
|
||||||
|
self.browser_details.clear()
|
||||||
|
|
||||||
|
# save browser details independent
|
||||||
|
# from main analysis UI settings
|
||||||
|
self.browser_root_type = root_type
|
||||||
|
self.browser_mode = out
|
||||||
|
|
||||||
|
root = self._new_browser_item(self.browser_root_type, self.browser, children=children)
|
||||||
|
|
||||||
|
self.browser.insertTopLevelItem(0, root)
|
||||||
|
|
||||||
|
def browser_item_selected(self, current, previous):
|
||||||
|
if not current:
|
||||||
|
# browser is being reset
|
||||||
|
return
|
||||||
|
|
||||||
|
self.log.debug("{0} selected in browser.".format(current.type_))
|
||||||
|
self.browser_details.clear()
|
||||||
|
|
||||||
|
try:
|
||||||
|
parent_type = current.parent().type_
|
||||||
|
except AttributeError:
|
||||||
|
# should only hit his on the root item
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
self.browser_details.appendPlainText("Domain Transitions {0} {1} {2}\n".format(
|
||||||
|
current.parent().type_, "->" if self.browser_mode else "<-", current.type_))
|
||||||
|
|
||||||
|
print_transition(self.browser_details.appendPlainText, current.rules)
|
||||||
|
|
||||||
|
self.browser_details.moveCursor(QTextCursor.Start)
|
||||||
|
|
||||||
|
if not current.child_populated:
|
||||||
|
self.busy.setLabelText("Gathering additional browser details for {0}...".format(
|
||||||
|
current.type_))
|
||||||
|
self.busy.show()
|
||||||
|
self.browser_thread.out = self.browser_mode
|
||||||
|
self.browser_thread.type_ = current.type_
|
||||||
|
self.browser_thread.start()
|
||||||
|
|
||||||
|
def add_browser_children(self, children):
|
||||||
|
item = self.browser.currentItem()
|
||||||
|
item.children = children
|
||||||
|
|
||||||
|
self.log.debug("Adding children for {0}".format(item.type_))
|
||||||
|
|
||||||
|
for child_type, child_rules in item.children:
|
||||||
|
child_item = self._new_browser_item(child_type, item, rules=child_rules)
|
||||||
|
item.addChild(child_item)
|
||||||
|
|
||||||
|
item.child_populated = True
|
||||||
|
self.busy.reset()
|
||||||
|
|
||||||
#
|
#
|
||||||
# Results runner
|
# Results runner
|
||||||
#
|
#
|
||||||
|
|
||||||
def run(self, button):
|
def run(self, button):
|
||||||
# right now there is only one button.
|
# right now there is only one button.
|
||||||
fail = False
|
fail = False
|
||||||
@ -234,10 +323,67 @@ class DomainTransitionAnalysisTab(SEToolsWidget, QScrollArea):
|
|||||||
self.busy.repaint()
|
self.busy.repaint()
|
||||||
self.raw_results.moveCursor(QTextCursor.Start)
|
self.raw_results.moveCursor(QTextCursor.Start)
|
||||||
|
|
||||||
|
if self.flows_in.isChecked() or self.flows_out.isChecked():
|
||||||
|
# move to browser tab for transitions in/out
|
||||||
|
self.results_frame.setCurrentIndex(1)
|
||||||
|
else:
|
||||||
|
self.results_frame.setCurrentIndex(0)
|
||||||
|
|
||||||
self.busy.reset()
|
self.busy.reset()
|
||||||
|
|
||||||
|
|
||||||
class ResultsUpdater(QObject):
|
def print_transition(renderer, trans):
|
||||||
|
"""
|
||||||
|
Raw rendering of a domain transition.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
renderer A callable which will render the output, e.g. print
|
||||||
|
trans A step (transition object) generated by the DTA.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if trans.transition:
|
||||||
|
renderer("Domain transition rule(s):")
|
||||||
|
for t in trans.transition:
|
||||||
|
renderer(str(t))
|
||||||
|
|
||||||
|
if trans.setexec:
|
||||||
|
renderer("\nSet execution context rule(s):")
|
||||||
|
for s in trans.setexec:
|
||||||
|
renderer(str(s))
|
||||||
|
|
||||||
|
for entrypoint in trans.entrypoints:
|
||||||
|
renderer("\nEntrypoint {0}:".format(entrypoint.name))
|
||||||
|
|
||||||
|
renderer("\tDomain entrypoint rule(s):")
|
||||||
|
for e in entrypoint.entrypoint:
|
||||||
|
renderer("\t{0}".format(e))
|
||||||
|
|
||||||
|
renderer("\n\tFile execute rule(s):")
|
||||||
|
for e in entrypoint.execute:
|
||||||
|
renderer("\t{0}".format(e))
|
||||||
|
|
||||||
|
if entrypoint.type_transition:
|
||||||
|
renderer("\n\tType transition rule(s):")
|
||||||
|
for t in entrypoint.type_transition:
|
||||||
|
renderer("\t{0}".format(t))
|
||||||
|
|
||||||
|
renderer("")
|
||||||
|
|
||||||
|
if trans.dyntransition:
|
||||||
|
renderer("Dynamic transition rule(s):")
|
||||||
|
for d in trans.dyntransition:
|
||||||
|
renderer(str(d))
|
||||||
|
|
||||||
|
renderer("\nSet current process context rule(s):")
|
||||||
|
for s in trans.setcurrent:
|
||||||
|
renderer(str(s))
|
||||||
|
|
||||||
|
renderer("")
|
||||||
|
|
||||||
|
renderer("")
|
||||||
|
|
||||||
|
|
||||||
|
class ResultsUpdater(QThread):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Thread for processing queries and updating result widgets.
|
Thread for processing queries and updating result widgets.
|
||||||
@ -247,22 +393,28 @@ class ResultsUpdater(QObject):
|
|||||||
model The model for the results
|
model The model for the results
|
||||||
|
|
||||||
Qt signals:
|
Qt signals:
|
||||||
finished The update has completed.
|
|
||||||
raw_line (str) A string to be appended to the raw results.
|
raw_line (str) A string to be appended to the raw results.
|
||||||
|
trans (str, bool, list) Initial information for populating
|
||||||
|
the transitions browser.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
finished = pyqtSignal()
|
|
||||||
raw_line = pyqtSignal(str)
|
raw_line = pyqtSignal(str)
|
||||||
|
trans = pyqtSignal(str, bool, list)
|
||||||
|
|
||||||
def __init__(self, query):
|
def __init__(self, query):
|
||||||
super(ResultsUpdater, self).__init__()
|
super(ResultsUpdater, self).__init__()
|
||||||
self.query = query
|
self.query = query
|
||||||
self.log = logging.getLogger(__name__)
|
self.log = logging.getLogger(__name__)
|
||||||
|
|
||||||
def update(self):
|
def __del__(self):
|
||||||
|
self.wait()
|
||||||
|
|
||||||
|
def run(self):
|
||||||
"""Run the query and update results."""
|
"""Run the query and update results."""
|
||||||
|
|
||||||
assert self.query.limit, "Code doesn't currently handle unlimited (limit=0) paths."
|
assert self.query.limit, "Code doesn't currently handle unlimited (limit=0) paths."
|
||||||
|
self.out = self.query.mode == "flows_out"
|
||||||
|
|
||||||
if self.query.mode == "all_paths":
|
if self.query.mode == "all_paths":
|
||||||
self.transitive(self.query.all_paths(self.query.source, self.query.target,
|
self.transitive(self.query.all_paths(self.query.source, self.query.target,
|
||||||
self.query.max_path_len))
|
self.query.max_path_len))
|
||||||
@ -273,52 +425,6 @@ class ResultsUpdater(QObject):
|
|||||||
else: # flows_in
|
else: # flows_in
|
||||||
self.direct(self.query.transitions(self.query.target))
|
self.direct(self.query.transitions(self.query.target))
|
||||||
|
|
||||||
self.finished.emit()
|
|
||||||
|
|
||||||
def print_transition(self, trans):
|
|
||||||
"""Raw rendering of a domain transition."""
|
|
||||||
|
|
||||||
if trans.transition:
|
|
||||||
self.raw_line.emit("Domain transition rule(s):")
|
|
||||||
for t in trans.transition:
|
|
||||||
self.raw_line.emit(str(t))
|
|
||||||
|
|
||||||
if trans.setexec:
|
|
||||||
self.raw_line.emit("\nSet execution context rule(s):")
|
|
||||||
for s in trans.setexec:
|
|
||||||
self.raw_line.emit(str(s))
|
|
||||||
|
|
||||||
for entrypoint in trans.entrypoints:
|
|
||||||
self.raw_line.emit("\nEntrypoint {0}:".format(entrypoint.name))
|
|
||||||
|
|
||||||
self.raw_line.emit("\tDomain entrypoint rule(s):")
|
|
||||||
for e in entrypoint.entrypoint:
|
|
||||||
self.raw_line.emit("\t{0}".format(e))
|
|
||||||
|
|
||||||
self.raw_line.emit("\n\tFile execute rule(s):")
|
|
||||||
for e in entrypoint.execute:
|
|
||||||
self.raw_line.emit("\t{0}".format(e))
|
|
||||||
|
|
||||||
if entrypoint.type_transition:
|
|
||||||
self.raw_line.emit("\n\tType transition rule(s):")
|
|
||||||
for t in entrypoint.type_transition:
|
|
||||||
self.raw_line.emit("\t{0}".format(t))
|
|
||||||
|
|
||||||
self.raw_line.emit("")
|
|
||||||
|
|
||||||
if trans.dyntransition:
|
|
||||||
self.raw_line.emit("Dynamic transition rule(s):")
|
|
||||||
for d in trans.dyntransition:
|
|
||||||
self.raw_line.emit(str(d))
|
|
||||||
|
|
||||||
self.raw_line.emit("\nSet current process context rule(s):")
|
|
||||||
for s in trans.setcurrent:
|
|
||||||
self.raw_line.emit(str(s))
|
|
||||||
|
|
||||||
self.raw_line.emit("")
|
|
||||||
|
|
||||||
self.raw_line.emit("")
|
|
||||||
|
|
||||||
def transitive(self, paths):
|
def transitive(self, paths):
|
||||||
i = 0
|
i = 0
|
||||||
for i, path in enumerate(paths, start=1):
|
for i, path in enumerate(paths, start=1):
|
||||||
@ -328,7 +434,7 @@ class ResultsUpdater(QObject):
|
|||||||
|
|
||||||
self.raw_line.emit("Step {0}: {1} -> {2}\n".format(stepnum, step.source,
|
self.raw_line.emit("Step {0}: {1} -> {2}\n".format(stepnum, step.source,
|
||||||
step.target))
|
step.target))
|
||||||
self.print_transition(step)
|
print_transition(self.raw_line.emit, step)
|
||||||
|
|
||||||
if QThread.currentThread().isInterruptionRequested() or (i >= self.query.limit):
|
if QThread.currentThread().isInterruptionRequested() or (i >= self.query.limit):
|
||||||
break
|
break
|
||||||
@ -340,14 +446,72 @@ class ResultsUpdater(QObject):
|
|||||||
|
|
||||||
def direct(self, transitions):
|
def direct(self, transitions):
|
||||||
i = 0
|
i = 0
|
||||||
|
child_types = []
|
||||||
for i, step in enumerate(transitions, start=1):
|
for i, step in enumerate(transitions, start=1):
|
||||||
self.raw_line.emit("Transition {0}: {1} -> {2}\n".format(i, step.source, step.target))
|
self.raw_line.emit("Transition {0}: {1} -> {2}\n".format(i, step.source, step.target))
|
||||||
self.print_transition(step)
|
print_transition(self.raw_line.emit, step)
|
||||||
|
|
||||||
if QThread.currentThread().isInterruptionRequested() or (i >= self.query.limit):
|
# Generate results for flow browser
|
||||||
|
if self.out:
|
||||||
|
child_types.append((step.target, step))
|
||||||
|
else:
|
||||||
|
child_types.append((step.source, step))
|
||||||
|
|
||||||
|
if QThread.currentThread().isInterruptionRequested():
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
QThread.yieldCurrentThread()
|
QThread.yieldCurrentThread()
|
||||||
|
|
||||||
self.raw_line.emit("{0} domain transition(s) found.".format(i))
|
self.raw_line.emit("{0} domain transition(s) found.".format(i))
|
||||||
self.log.info("{0} domain transition(s) found.".format(i))
|
self.log.info("{0} domain transition(s) found.".format(i))
|
||||||
|
|
||||||
|
# Update browser:
|
||||||
|
root_type = self.query.source if self.out else self.query.target
|
||||||
|
self.trans.emit(str(root_type), self.out, sorted(child_types))
|
||||||
|
|
||||||
|
|
||||||
|
class BrowserUpdater(QThread):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Thread for processing additional analysis for the browser.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
query The query object
|
||||||
|
model The model for the results
|
||||||
|
|
||||||
|
Qt signals:
|
||||||
|
trans A list of child types to render in the
|
||||||
|
transitions browser.
|
||||||
|
"""
|
||||||
|
|
||||||
|
trans = pyqtSignal(list)
|
||||||
|
|
||||||
|
def __init__(self, query):
|
||||||
|
super(BrowserUpdater, self).__init__()
|
||||||
|
self.query = query
|
||||||
|
self.type_ = None
|
||||||
|
self.out = None
|
||||||
|
self.log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
def __del__(self):
|
||||||
|
self.wait()
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
transnum = 0
|
||||||
|
child_types = []
|
||||||
|
for transnum, trans in enumerate(self.query.transitions(self.type_), start=1):
|
||||||
|
# Generate results for browser
|
||||||
|
if self.out:
|
||||||
|
child_types.append((trans.target, trans))
|
||||||
|
else:
|
||||||
|
child_types.append((trans.source, trans))
|
||||||
|
|
||||||
|
if QThread.currentThread().isInterruptionRequested():
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
QThread.yieldCurrentThread()
|
||||||
|
|
||||||
|
self.log.debug("{0} additional domain transition(s) found.".format(transnum))
|
||||||
|
|
||||||
|
# Update browser:
|
||||||
|
self.trans.emit(sorted(child_types))
|
||||||
|
@ -20,9 +20,10 @@
|
|||||||
import logging
|
import logging
|
||||||
import copy
|
import copy
|
||||||
|
|
||||||
from PyQt5.QtCore import pyqtSignal, Qt, QObject, QStringListModel, QThread
|
from PyQt5.QtCore import pyqtSignal, Qt, QStringListModel, QThread
|
||||||
from PyQt5.QtGui import QPalette, QTextCursor
|
from PyQt5.QtGui import QPalette, QTextCursor
|
||||||
from PyQt5.QtWidgets import QCompleter, QHeaderView, QMessageBox, QProgressDialog, QScrollArea
|
from PyQt5.QtWidgets import QCompleter, QHeaderView, QMessageBox, QProgressDialog, QScrollArea, \
|
||||||
|
QTreeWidgetItem
|
||||||
from setools import InfoFlowAnalysis
|
from setools import InfoFlowAnalysis
|
||||||
from setools.exception import UnmappedClass, UnmappedPermission
|
from setools.exception import UnmappedClass, UnmappedPermission
|
||||||
|
|
||||||
@ -100,13 +101,14 @@ class InfoFlowAnalysisTab(SEToolsWidget, QScrollArea):
|
|||||||
self.clear_target_error()
|
self.clear_target_error()
|
||||||
|
|
||||||
# set up processing thread
|
# set up processing thread
|
||||||
self.thread = QThread()
|
self.thread = ResultsUpdater(self.query)
|
||||||
self.worker = ResultsUpdater(self.query)
|
self.thread.raw_line.connect(self.raw_results.appendPlainText)
|
||||||
self.worker.moveToThread(self.thread)
|
self.thread.finished.connect(self.update_complete)
|
||||||
self.worker.raw_line.connect(self.raw_results.appendPlainText)
|
self.thread.flows.connect(self.reset_browser)
|
||||||
self.worker.finished.connect(self.update_complete)
|
|
||||||
self.worker.finished.connect(self.thread.quit)
|
# set up browser thread
|
||||||
self.thread.started.connect(self.worker.update)
|
self.browser_thread = BrowserUpdater(self.query)
|
||||||
|
self.browser_thread.flows.connect(self.add_browser_children)
|
||||||
|
|
||||||
# create a "busy, please wait" dialog
|
# create a "busy, please wait" dialog
|
||||||
self.busy = QProgressDialog(self)
|
self.busy = QProgressDialog(self)
|
||||||
@ -127,6 +129,7 @@ class InfoFlowAnalysisTab(SEToolsWidget, QScrollArea):
|
|||||||
self.target.setEnabled(not self.flows_out.isChecked())
|
self.target.setEnabled(not self.flows_out.isChecked())
|
||||||
self.criteria_frame.setHidden(not self.criteria_expander.isChecked())
|
self.criteria_frame.setHidden(not self.criteria_expander.isChecked())
|
||||||
self.notes.setHidden(not self.notes_expander.isChecked())
|
self.notes.setHidden(not self.notes_expander.isChecked())
|
||||||
|
self.browser_tab.setEnabled(self.flows_in.isChecked() or self.flows_out.isChecked())
|
||||||
|
|
||||||
# connect signals
|
# connect signals
|
||||||
self.buttonBox.clicked.connect(self.run)
|
self.buttonBox.clicked.connect(self.run)
|
||||||
@ -140,6 +143,7 @@ class InfoFlowAnalysisTab(SEToolsWidget, QScrollArea):
|
|||||||
self.min_perm_weight.valueChanged.connect(self.set_min_weight)
|
self.min_perm_weight.valueChanged.connect(self.set_min_weight)
|
||||||
self.exclude_types.clicked.connect(self.choose_excluded_types)
|
self.exclude_types.clicked.connect(self.choose_excluded_types)
|
||||||
self.edit_permmap.clicked.connect(self.open_permmap_editor)
|
self.edit_permmap.clicked.connect(self.open_permmap_editor)
|
||||||
|
self.browser.currentItemChanged.connect(self.browser_item_selected)
|
||||||
|
|
||||||
#
|
#
|
||||||
# Analysis mode
|
# Analysis mode
|
||||||
@ -153,11 +157,15 @@ class InfoFlowAnalysisTab(SEToolsWidget, QScrollArea):
|
|||||||
self.clear_source_error()
|
self.clear_source_error()
|
||||||
self.clear_target_error()
|
self.clear_target_error()
|
||||||
self.source.setEnabled(not value)
|
self.source.setEnabled(not value)
|
||||||
|
self.limit_paths.setEnabled(not value)
|
||||||
|
self.browser_tab.setEnabled(value)
|
||||||
|
|
||||||
def flows_out_toggled(self, value):
|
def flows_out_toggled(self, value):
|
||||||
self.clear_source_error()
|
self.clear_source_error()
|
||||||
self.clear_target_error()
|
self.clear_target_error()
|
||||||
self.target.setEnabled(not value)
|
self.target.setEnabled(not value)
|
||||||
|
self.limit_paths.setEnabled(not value)
|
||||||
|
self.browser_tab.setEnabled(value)
|
||||||
|
|
||||||
#
|
#
|
||||||
# Source criteria
|
# Source criteria
|
||||||
@ -222,10 +230,92 @@ class InfoFlowAnalysisTab(SEToolsWidget, QScrollArea):
|
|||||||
# used only by permission map editor
|
# used only by permission map editor
|
||||||
self.query.perm_map = pmap
|
self.query.perm_map = pmap
|
||||||
|
|
||||||
|
#
|
||||||
|
# Infoflow browser
|
||||||
|
#
|
||||||
|
def _new_browser_item(self, type_, parent, rules=None, children=None):
|
||||||
|
# build main item
|
||||||
|
item = QTreeWidgetItem(parent if parent else self.browser)
|
||||||
|
item.setText(0, str(type_))
|
||||||
|
item.type_ = type_
|
||||||
|
item.children = children if children else []
|
||||||
|
item.rules = rules if rules else []
|
||||||
|
item.child_populated = children is not None
|
||||||
|
|
||||||
|
# add child items
|
||||||
|
for child_type, child_rules in item.children:
|
||||||
|
child_item = self._new_browser_item(child_type, item, rules=child_rules)
|
||||||
|
item.addChild(child_item)
|
||||||
|
|
||||||
|
item.setExpanded(children is not None)
|
||||||
|
|
||||||
|
self.log.debug("Built item for {0} with {1} children and {2} rules".format(
|
||||||
|
type_, len(item.children), len(item.rules)))
|
||||||
|
|
||||||
|
return item
|
||||||
|
|
||||||
|
def reset_browser(self, root_type, out, children):
|
||||||
|
self.log.debug("Resetting browser.")
|
||||||
|
|
||||||
|
# clear results
|
||||||
|
self.browser.clear()
|
||||||
|
self.browser_details.clear()
|
||||||
|
|
||||||
|
# save browser details independent
|
||||||
|
# from main analysis UI settings
|
||||||
|
self.browser_root_type = root_type
|
||||||
|
self.browser_mode = out
|
||||||
|
|
||||||
|
root = self._new_browser_item(self.browser_root_type, self.browser, children=children)
|
||||||
|
|
||||||
|
self.browser.insertTopLevelItem(0, root)
|
||||||
|
|
||||||
|
def browser_item_selected(self, current, previous):
|
||||||
|
if not current:
|
||||||
|
# browser is being reset
|
||||||
|
return
|
||||||
|
|
||||||
|
self.log.debug("{0} selected in browser.".format(current.type_))
|
||||||
|
self.browser_details.clear()
|
||||||
|
|
||||||
|
try:
|
||||||
|
parent_type = current.parent().type_
|
||||||
|
except AttributeError:
|
||||||
|
# should only hit his on the root item
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
self.browser_details.appendPlainText("Information flows {0} {1} {2}\n".format(
|
||||||
|
current.parent().type_, "->" if self.browser_mode else "<-", current.type_))
|
||||||
|
|
||||||
|
for rule in current.rules:
|
||||||
|
self.browser_details.appendPlainText(rule)
|
||||||
|
|
||||||
|
self.browser_details.moveCursor(QTextCursor.Start)
|
||||||
|
|
||||||
|
if not current.child_populated:
|
||||||
|
self.busy.setLabelText("Gathering additional browser details for {0}...".format(
|
||||||
|
current.type_))
|
||||||
|
self.busy.show()
|
||||||
|
self.browser_thread.out = self.browser_mode
|
||||||
|
self.browser_thread.type_ = current.type_
|
||||||
|
self.browser_thread.start()
|
||||||
|
|
||||||
|
def add_browser_children(self, children):
|
||||||
|
item = self.browser.currentItem()
|
||||||
|
item.children = children
|
||||||
|
|
||||||
|
self.log.debug("Adding children for {0}".format(item.type_))
|
||||||
|
|
||||||
|
for child_type, child_rules in item.children:
|
||||||
|
child_item = self._new_browser_item(child_type, item, rules=child_rules)
|
||||||
|
item.addChild(child_item)
|
||||||
|
|
||||||
|
item.child_populated = True
|
||||||
|
self.busy.reset()
|
||||||
|
|
||||||
#
|
#
|
||||||
# Results runner
|
# Results runner
|
||||||
#
|
#
|
||||||
|
|
||||||
def run(self, button):
|
def run(self, button):
|
||||||
# right now there is only one button.
|
# right now there is only one button.
|
||||||
fail = False
|
fail = False
|
||||||
@ -267,10 +357,16 @@ class InfoFlowAnalysisTab(SEToolsWidget, QScrollArea):
|
|||||||
self.busy.repaint()
|
self.busy.repaint()
|
||||||
self.raw_results.moveCursor(QTextCursor.Start)
|
self.raw_results.moveCursor(QTextCursor.Start)
|
||||||
|
|
||||||
|
if self.flows_in.isChecked() or self.flows_out.isChecked():
|
||||||
|
# move to browser tab for flows in/out
|
||||||
|
self.results_frame.setCurrentIndex(1)
|
||||||
|
else:
|
||||||
|
self.results_frame.setCurrentIndex(0)
|
||||||
|
|
||||||
self.busy.reset()
|
self.busy.reset()
|
||||||
|
|
||||||
|
|
||||||
class ResultsUpdater(QObject):
|
class ResultsUpdater(QThread):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Thread for processing queries and updating result widgets.
|
Thread for processing queries and updating result widgets.
|
||||||
@ -280,33 +376,37 @@ class ResultsUpdater(QObject):
|
|||||||
model The model for the results
|
model The model for the results
|
||||||
|
|
||||||
Qt signals:
|
Qt signals:
|
||||||
finished The update has completed.
|
raw_line A string to be appended to the raw results.
|
||||||
raw_line (str) A string to be appended to the raw results.
|
flows (str, bool, list) Initial information for populating
|
||||||
|
the flows browser.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
finished = pyqtSignal()
|
|
||||||
raw_line = pyqtSignal(str)
|
raw_line = pyqtSignal(str)
|
||||||
|
flows = pyqtSignal(str, bool, list)
|
||||||
|
|
||||||
def __init__(self, query):
|
def __init__(self, query):
|
||||||
super(ResultsUpdater, self).__init__()
|
super(ResultsUpdater, self).__init__()
|
||||||
self.query = query
|
self.query = query
|
||||||
self.log = logging.getLogger(__name__)
|
self.log = logging.getLogger(__name__)
|
||||||
|
|
||||||
def update(self):
|
def __del__(self):
|
||||||
|
self.wait()
|
||||||
|
|
||||||
|
def run(self):
|
||||||
"""Run the query and update results."""
|
"""Run the query and update results."""
|
||||||
|
|
||||||
assert self.query.limit, "Code doesn't currently handle unlimited (limit=0) paths."
|
assert self.query.limit, "Code doesn't currently handle unlimited (limit=0) paths."
|
||||||
|
self.out = self.query.mode == "flows_out"
|
||||||
|
|
||||||
if self.query.mode == "all_paths":
|
if self.query.mode == "all_paths":
|
||||||
self.transitive(self.query.all_paths(self.query.source, self.query.target,
|
self.transitive(self.query.all_paths(self.query.source, self.query.target,
|
||||||
self.query.max_path_len))
|
self.query.max_path_len))
|
||||||
elif self.query.mode == "all_shortest_paths":
|
elif self.query.mode == "all_shortest_paths":
|
||||||
self.transitive(self.query.all_shortest_paths(self.query.source, self.query.target))
|
self.transitive(self.query.all_shortest_paths(self.query.source, self.query.target))
|
||||||
elif self.query.mode == "flows_out":
|
elif self.query.mode == "flows_out":
|
||||||
self.direct(self.query.infoflows(self.query.source, out=True))
|
self.direct(self.query.infoflows(self.query.source, out=self.out))
|
||||||
else: # flows_in
|
else: # flows_in
|
||||||
self.direct(self.query.infoflows(self.query.target, out=False))
|
self.direct(self.query.infoflows(self.query.target, out=self.out))
|
||||||
|
|
||||||
self.finished.emit()
|
|
||||||
|
|
||||||
def transitive(self, paths):
|
def transitive(self, paths):
|
||||||
pathnum = 0
|
pathnum = 0
|
||||||
@ -334,6 +434,7 @@ class ResultsUpdater(QObject):
|
|||||||
|
|
||||||
def direct(self, flows):
|
def direct(self, flows):
|
||||||
flownum = 0
|
flownum = 0
|
||||||
|
child_types = []
|
||||||
for flownum, flow in enumerate(flows, start=1):
|
for flownum, flow in enumerate(flows, start=1):
|
||||||
self.raw_line.emit("Flow {0}: {1} -> {2}".format(flownum, flow.source, flow.target))
|
self.raw_line.emit("Flow {0}: {1} -> {2}".format(flownum, flow.source, flow.target))
|
||||||
for rule in sorted(flow.rules):
|
for rule in sorted(flow.rules):
|
||||||
@ -341,10 +442,67 @@ class ResultsUpdater(QObject):
|
|||||||
|
|
||||||
self.raw_line.emit("")
|
self.raw_line.emit("")
|
||||||
|
|
||||||
if QThread.currentThread().isInterruptionRequested() or (flownum >= self.query.limit):
|
# Generate results for flow browser
|
||||||
|
if self.out:
|
||||||
|
child_types.append((flow.target, sorted(str(r) for r in flow.rules)))
|
||||||
|
else:
|
||||||
|
child_types.append((flow.source, sorted(str(r) for r in flow.rules)))
|
||||||
|
|
||||||
|
if QThread.currentThread().isInterruptionRequested():
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
QThread.yieldCurrentThread()
|
QThread.yieldCurrentThread()
|
||||||
|
|
||||||
self.raw_line.emit("{0} information flow(s) found.\n".format(flownum))
|
self.raw_line.emit("{0} information flow(s) found.\n".format(flownum))
|
||||||
self.log.info("{0} information flow(s) found.".format(flownum))
|
self.log.info("{0} information flow(s) found.".format(flownum))
|
||||||
|
|
||||||
|
# Update browser:
|
||||||
|
root_type = self.query.source if self.out else self.query.target
|
||||||
|
self.flows.emit(str(root_type), self.out, sorted(child_types))
|
||||||
|
|
||||||
|
|
||||||
|
class BrowserUpdater(QThread):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Thread for processing additional analysis for the browser.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
query The query object
|
||||||
|
model The model for the results
|
||||||
|
|
||||||
|
Qt signals:
|
||||||
|
flows A list of child types to render in the
|
||||||
|
infoflows browser.
|
||||||
|
"""
|
||||||
|
|
||||||
|
flows = pyqtSignal(list)
|
||||||
|
|
||||||
|
def __init__(self, query):
|
||||||
|
super(BrowserUpdater, self).__init__()
|
||||||
|
self.query = query
|
||||||
|
self.type_ = None
|
||||||
|
self.out = None
|
||||||
|
self.log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
def __del__(self):
|
||||||
|
self.wait()
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
flownum = 0
|
||||||
|
child_types = []
|
||||||
|
for flownum, flow in enumerate(self.query.infoflows(self.type_, out=self.out), start=1):
|
||||||
|
# Generate results for flow browser
|
||||||
|
if self.out:
|
||||||
|
child_types.append((flow.target, sorted(str(r) for r in flow.rules)))
|
||||||
|
else:
|
||||||
|
child_types.append((flow.source, sorted(str(r) for r in flow.rules)))
|
||||||
|
|
||||||
|
if QThread.currentThread().isInterruptionRequested():
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
QThread.yieldCurrentThread()
|
||||||
|
|
||||||
|
self.log.debug("{0} additional information flow(s) found.".format(flownum))
|
||||||
|
|
||||||
|
# Update browser:
|
||||||
|
self.flows.emit(sorted(child_types))
|
||||||
|
Loading…
Reference in New Issue
Block a user