Implement UserQueryTab

Closes #90
This commit is contained in:
Chris PeBenito 2016-02-25 10:03:20 -05:00
parent a1fbce87da
commit 16e03a3960
6 changed files with 1124 additions and 30 deletions

51
data/detail_popup.ui Normal file
View File

@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>DetailsPopup</class>
<widget class="QDialog" name="DetailsPopup">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>300</height>
</rect>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QTextBrowser" name="contents"/>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Close</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>clicked(QAbstractButton*)</signal>
<receiver>DetailsPopup</receiver>
<slot>close()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

633
data/userquery.ui Normal file
View File

@ -0,0 +1,633 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>UserQueryTab</class>
<widget class="QScrollArea" name="UserQueryTab">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>774</width>
<height>846</height>
</rect>
</property>
<property name="sizeAdjustPolicy">
<enum>QAbstractScrollArea::AdjustToContents</enum>
</property>
<property name="widgetResizable">
<bool>true</bool>
</property>
<widget class="QWidget" name="contents">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>772</width>
<height>844</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<layout class="QGridLayout" name="gridLayout_4">
<item row="0" column="0" colspan="2">
<widget class="QLabel" name="label">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>20</height>
</size>
</property>
<property name="font">
<font>
<pointsize>11</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>SELinux Users</string>
</property>
</widget>
</item>
<item row="1" column="0" colspan="2">
<widget class="QSplitter" name="browser_search_splitter">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<widget class="QGroupBox" name="browser_groupBox">
<property name="title">
<string>User Browser</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="UserList" name="users"/>
</item>
</layout>
</widget>
<widget class="QSplitter" name="search_results_splitter">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<widget class="QGroupBox" name="criteria_groupBox">
<property name="maximumSize">
<size>
<width>16777215</width>
<height>16777215</height>
</size>
</property>
<property name="title">
<string>Search Criteria</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="2" column="0" colspan="2">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons">
<set>QDialogButtonBox::Apply</set>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QGroupBox" name="level_criteria">
<property name="maximumSize">
<size>
<width>16777215</width>
<height>120</height>
</size>
</property>
<property name="title">
<string>Default MLS Level</string>
</property>
<layout class="QGridLayout" name="gridLayout_5">
<property name="leftMargin">
<number>6</number>
</property>
<property name="topMargin">
<number>6</number>
</property>
<property name="rightMargin">
<number>6</number>
</property>
<property name="bottomMargin">
<number>6</number>
</property>
<property name="spacing">
<number>3</number>
</property>
<item row="1" column="1">
<widget class="QLineEdit" name="level">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>150</width>
<height>20</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>250</width>
<height>16777215</height>
</size>
</property>
</widget>
</item>
<item row="0" column="4">
<widget class="QRadioButton" name="level_exact">
<property name="toolTip">
<string>The level criterion will match if it is equal to the user's default level.</string>
</property>
<property name="text">
<string>Equal</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="4">
<widget class="QRadioButton" name="level_dom">
<property name="toolTip">
<string>The level criterion will match if it dominates the user's default level.</string>
</property>
<property name="text">
<string>Dominate</string>
</property>
</widget>
</item>
<item row="2" column="4">
<widget class="QRadioButton" name="level_domby">
<property name="toolTip">
<string>The level criterion will match if it is dominated by the user's default level.</string>
</property>
<property name="text">
<string>Dominated</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="1" column="1">
<widget class="QGroupBox" name="range_criteria">
<property name="maximumSize">
<size>
<width>16777215</width>
<height>120</height>
</size>
</property>
<property name="title">
<string>MLS Range</string>
</property>
<layout class="QGridLayout" name="gridLayout_6">
<property name="leftMargin">
<number>6</number>
</property>
<property name="topMargin">
<number>6</number>
</property>
<property name="rightMargin">
<number>6</number>
</property>
<property name="bottomMargin">
<number>6</number>
</property>
<property name="spacing">
<number>3</number>
</property>
<item row="1" column="1">
<widget class="QLineEdit" name="range_">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>150</width>
<height>20</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>250</width>
<height>16777215</height>
</size>
</property>
</widget>
</item>
<item row="0" column="4">
<widget class="QRadioButton" name="range_exact">
<property name="toolTip">
<string>The range criterion will match if it is equal to the user's range.</string>
</property>
<property name="text">
<string>Equal</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="4">
<widget class="QRadioButton" name="range_subset">
<property name="toolTip">
<string>The range criterion will match if it is a subset of the user's range.</string>
</property>
<property name="text">
<string>Subset</string>
</property>
</widget>
</item>
<item row="1" column="4">
<widget class="QRadioButton" name="range_overlap">
<property name="toolTip">
<string>The range criterion will match if it overlaps the user's range.</string>
</property>
<property name="text">
<string>Overlap</string>
</property>
</widget>
</item>
<item row="3" column="4">
<widget class="QRadioButton" name="range_superset">
<property name="toolTip">
<string>The range criterion will match if it is a superset of the user's range.</string>
</property>
<property name="text">
<string>Superset</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="0" column="0" rowspan="2">
<widget class="QGroupBox" name="role_criteria">
<property name="title">
<string>Roles</string>
</property>
<layout class="QGridLayout" name="gridLayout_7">
<property name="leftMargin">
<number>6</number>
</property>
<property name="topMargin">
<number>6</number>
</property>
<property name="rightMargin">
<number>6</number>
</property>
<property name="bottomMargin">
<number>6</number>
</property>
<property name="spacing">
<number>3</number>
</property>
<item row="2" column="1">
<widget class="QPushButton" name="clear_roles">
<property name="text">
<string>Clear</string>
</property>
</widget>
</item>
<item row="2" column="2">
<spacer name="horizontalSpacer_4">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="4" column="1">
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="3" column="1">
<widget class="QPushButton" name="invert_roles">
<property name="text">
<string>Invert</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QRadioButton" name="roles_equal">
<property name="toolTip">
<string>A matching user will have a role set equal to the selected roles.</string>
</property>
<property name="text">
<string>Equal</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QRadioButton" name="roles_any">
<property name="toolTip">
<string>A matching user will have any of the selected roles.</string>
</property>
<property name="text">
<string>Any</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="0" rowspan="5">
<widget class="QListView" name="roles">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>250</width>
<height>16777215</height>
</size>
</property>
<property name="toolTip">
<string>Match the role set of the user.</string>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::ExtendedSelection</enum>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<widget class="QTabWidget" name="results_frame">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="table_page">
<attribute name="title">
<string>Results</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_3">
<property name="leftMargin">
<number>6</number>
</property>
<property name="topMargin">
<number>6</number>
</property>
<property name="rightMargin">
<number>6</number>
</property>
<property name="bottomMargin">
<number>6</number>
</property>
<item>
<widget class="QTableView" name="table_results">
<property name="sizeAdjustPolicy">
<enum>QAbstractScrollArea::AdjustIgnored</enum>
</property>
<property name="alternatingRowColors">
<bool>true</bool>
</property>
<property name="sortingEnabled">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="raw_page">
<attribute name="title">
<string>Raw Results</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="leftMargin">
<number>6</number>
</property>
<property name="topMargin">
<number>6</number>
</property>
<property name="rightMargin">
<number>6</number>
</property>
<property name="bottomMargin">
<number>6</number>
</property>
<item>
<widget class="QPlainTextEdit" name="raw_results">
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="font">
<font>
<family>Monospace</family>
</font>
</property>
<property name="documentTitle">
<string/>
</property>
<property name="lineWrapMode">
<enum>QPlainTextEdit::NoWrap</enum>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</widget>
</widget>
</item>
<item row="2" column="0">
<widget class="QPushButton" name="notes_expander">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>16</width>
<height>16</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16</width>
<height>16</height>
</size>
</property>
<property name="focusPolicy">
<enum>Qt::TabFocus</enum>
</property>
<property name="styleSheet">
<string notr="true">QPushButton:flat { border: none; }</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset>
<normaloff>icons/expand_inactive.png</normaloff>
<normalon>icons/expand_active.png</normalon>icons/expand_inactive.png</iconset>
</property>
<property name="iconSize">
<size>
<width>16</width>
<height>16</height>
</size>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>false</bool>
</property>
<property name="flat">
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLabel" name="label_4">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Notes</string>
</property>
</widget>
</item>
<item row="3" column="0" colspan="2">
<widget class="QTextEdit" name="notes">
<property name="minimumSize">
<size>
<width>0</width>
<height>125</height>
</size>
</property>
<property name="toolTip">
<string>Optionally enter notes here about the query.</string>
</property>
</widget>
</item>
</layout>
<zorder>browser_search_splitter</zorder>
<zorder>notes</zorder>
<zorder>label</zorder>
<zorder>notes_expander</zorder>
<zorder>label_4</zorder>
</widget>
</widget>
<customwidgets>
<customwidget>
<class>UserList</class>
<extends>QListView</extends>
<header>setoolsgui/apol/usermodel.h</header>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>users</tabstop>
<tabstop>roles</tabstop>
<tabstop>roles_any</tabstop>
<tabstop>roles_equal</tabstop>
<tabstop>clear_roles</tabstop>
<tabstop>invert_roles</tabstop>
<tabstop>level</tabstop>
<tabstop>level_exact</tabstop>
<tabstop>level_dom</tabstop>
<tabstop>level_domby</tabstop>
<tabstop>range_</tabstop>
<tabstop>range_exact</tabstop>
<tabstop>range_overlap</tabstop>
<tabstop>range_subset</tabstop>
<tabstop>range_superset</tabstop>
<tabstop>results_frame</tabstop>
<tabstop>table_results</tabstop>
<tabstop>raw_results</tabstop>
<tabstop>notes_expander</tabstop>
<tabstop>notes</tabstop>
</tabstops>
<resources/>
<connections>
<connection>
<sender>notes_expander</sender>
<signal>toggled(bool)</signal>
<receiver>notes</receiver>
<slot>setVisible(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>17</x>
<y>833</y>
</hint>
<hint type="destinationlabel">
<x>379</x>
<y>910</y>
</hint>
</hints>
</connection>
<connection>
<sender>clear_roles</sender>
<signal>clicked()</signal>
<receiver>roles</receiver>
<slot>clearSelection()</slot>
<hints>
<hint type="sourcelabel">
<x>429</x>
<y>99</y>
</hint>
<hint type="destinationlabel">
<x>319</x>
<y>184</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -0,0 +1,60 @@
# 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.QtGui import QFont
from PyQt5.QtWidgets import QDialog
from ..widget import SEToolsWidget
class DetailsPopup(SEToolsWidget, QDialog):
"""A generic non-modal popup with a text field to write detailed info."""
# TODO: make the font changes relative
# instead of setting absolute values
def __init__(self, parent, title=None):
super(DetailsPopup, self).__init__(parent)
self.log = logging.getLogger(self.__class__.__name__)
self.setupUi(title)
def setupUi(self, title):
self.load_ui("detail_popup.ui")
if title:
self.title = title
@property
def title(self):
self.windowTitle(self)
@title.setter
def title(self, text):
self.setWindowTitle(text)
def append(self, text):
self.contents.setFontWeight(QFont.Normal)
self.contents.setFontPointSize(9)
self.contents.append(text)
def append_header(self, text):
self.contents.setFontWeight(QFont.Black)
self.contents.setFontPointSize(11)
self.contents.append(text)

View File

@ -31,6 +31,7 @@ from .infoflow import InfoFlowAnalysisTab
from .mlsrulequery import MLSRuleQueryTab
from .rbacrulequery import RBACRuleQueryTab
from .terulequery import TERuleQueryTab
from .userquery import UserQueryTab
class ApolMainWindow(SEToolsWidget, QMainWindow):
@ -197,42 +198,15 @@ class ChooseAnalysis(SEToolsWidget, QDialog):
The item_mapping attribute will be populated to
map the tree list items to the analysis tab widgets.
"""
# _components_map = {"Attributes (Type)": TERuleQueryTab,
# "Booleans": TERuleQueryTab,
# "Categories": TERuleQueryTab,
# "Common Permission Sets": TERuleQueryTab,
# "Object Classes": TERuleQueryTab,
# "Policy Capabilities": TERuleQueryTab,
# "Roles": TERuleQueryTab,
# "Types": TERuleQueryTab,
# "Users": TERuleQueryTab}
#
# _rule_map = {"TE Rules": TERuleQueryTab,
# "RBAC Rules": TERuleQueryTab,
# "MLS Rules": TERuleQueryTab,
# "Constraints": TERuleQueryTab}
#
# _analysis_map = {"Domain Transition Analysis": TERuleQueryTab,
# "Information Flow Analysis": TERuleQueryTab}
#
# _labeling_map = {"fs_use Statements": TERuleQueryTab,
# "Genfscon Statements": TERuleQueryTab,
# "Initial SID Statements": TERuleQueryTab,
# "Netifcon Statements": TERuleQueryTab,
# "Nodecon Statements": TERuleQueryTab,
# "Portcon Statements": TERuleQueryTab}
#
# _analysis_choices = {"Components": _components_map,
# "Rules": _rule_map,
# "Analysis": _analysis_map,
# "Labeling Statements": _labeling_map}
_analysis_map = {"Domain Transition Analysis": DomainTransitionAnalysisTab,
"Information Flow Analysis": InfoFlowAnalysisTab}
_components_map = {"Users": UserQueryTab}
_rule_map = {"MLS Rules": MLSRuleQueryTab,
"RBAC Rules": RBACRuleQueryTab,
"TE Rules": TERuleQueryTab}
_analysis_choices = {"Rules": _rule_map,
_analysis_choices = {"Components": _components_map,
"Rules": _rule_map,
"Analyses": _analysis_map}
def __init__(self, parent):

View File

@ -0,0 +1,132 @@
# 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 PyQt5.QtCore import Qt, QAbstractTableModel, QModelIndex
from PyQt5.QtGui import QCursor
from PyQt5.QtWidgets import QAction, QListView, QMenu
from setools.policyrep.exception import MLSDisabled
from .details import DetailsPopup
def user_detail(parent, user):
"""
Create a dialog box for user details.
Parameters:
parent The parent Qt Widget
user The user
"""
detail = DetailsPopup(parent, "SELinux user detail: {0}".format(user))
detail.append_header("Roles:")
for r in user.roles:
detail.append(" {0}".format(r))
try:
l = user.mls_level
r = user.mls_range
except MLSDisabled:
pass
else:
detail.append_header("\nDefault MLS Level:")
detail.append(" {0}".format(l))
detail.append_header("\nMLS Range:")
detail.append(" {0}".format(r))
detail.show()
class UserList(QListView):
"""A QListView widget for listing users."""
def __init__(self, parent):
super(UserList, self).__init__(parent)
# set up right-click context menu
self.get_detail = QAction("More details...", self)
self.menu = QMenu(self)
self.menu.addAction(self.get_detail)
def contextMenuEvent(self, event):
self.menu.popup(QCursor.pos())
class UserTableModel(QAbstractTableModel):
"""Table-based model for users."""
def __init__(self, parent, mls):
super(UserTableModel, self).__init__(parent)
self.resultlist = []
self.mls = mls
def headerData(self, section, orientation, role):
if role == Qt.DisplayRole and orientation == Qt.Horizontal:
if section == 0:
return "User Name"
elif section == 1:
return "Roles"
elif section == 2:
return "Default Level"
elif section == 3:
return "Range"
else:
raise ValueError("Invalid column number: {0}".format(section))
def columnCount(self, parent=QModelIndex()):
if self.mls:
return 4
else:
return 2
def rowCount(self, parent=QModelIndex()):
if self.resultlist:
return len(self.resultlist)
else:
return 0
def data(self, index, role):
if role == Qt.DisplayRole:
if not self.resultlist:
return None
row = index.row()
col = index.column()
if col == 0:
return str(self.resultlist[row])
elif col == 1:
return ", ".join(sorted(str(r) for r in self.resultlist[row].roles))
elif col == 2:
try:
return str(self.resultlist[row].mls_level)
except MLSDisabled:
return None
elif col == 3:
try:
return str(self.resultlist[row].mls_range)
except MLSDisabled:
return None
else:
raise ValueError("Invalid column number: {0}".format(col))
elif role == Qt.UserRole:
# get the whole rule for user role
return self.resultlist[row].statement()

View File

@ -0,0 +1,244 @@
# 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 UserQuery
from ..widget import SEToolsWidget
from .models import SEToolsListModel, invert_list_selection
from .usermodel import UserTableModel, user_detail
class UserQueryTab(SEToolsWidget, QScrollArea):
"""User browser and query tab."""
def __init__(self, parent, policy, perm_map):
super(UserQueryTab, self).__init__(parent)
self.log = logging.getLogger(self.__class__.__name__)
self.policy = policy
self.query = UserQuery(policy)
self.setupUi()
def __del__(self):
self.thread.quit()
self.thread.wait(5000)
def setupUi(self):
self.load_ui("userquery.ui")
# populate user list
self.user_model = SEToolsListModel(self)
self.user_model.item_list = sorted(self.policy.users())
self.users.setModel(self.user_model)
# populate role list
self.role_model = SEToolsListModel(self)
self.role_model.item_list = sorted(r for r in self.policy.roles() if r != "object_r")
self.roles.setModel(self.role_model)
# set up results
self.table_results_model = UserTableModel(self, self.policy.mls)
self.sort_proxy = QSortFilterProxyModel(self)
self.sort_proxy.setSourceModel(self.table_results_model)
self.table_results.setModel(self.sort_proxy)
if self.policy.mls:
# setup indications of errors on level/range
self.orig_palette = self.level.palette()
self.error_palette = self.level.palette()
self.error_palette.setColor(QPalette.Base, Qt.red)
self.clear_level_error()
self.clear_range_error()
else:
# hide level and range criteria
self.level_criteria.setHidden(True)
self.range_criteria.setHidden(True)
# 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)
# Ensure settings are consistent with the initial .ui state
self.notes.setHidden(not self.notes_expander.isChecked())
# connect signals
self.users.doubleClicked.connect(self.get_detail)
self.users.get_detail.triggered.connect(self.get_detail)
self.roles.selectionModel().selectionChanged.connect(self.set_roles)
self.invert_roles.clicked.connect(self.invert_role_selection)
self.level.textEdited.connect(self.clear_level_error)
self.level.editingFinished.connect(self.set_level)
self.range_.textEdited.connect(self.clear_range_error)
self.range_.editingFinished.connect(self.set_range)
self.buttonBox.clicked.connect(self.run)
#
# User browser
#
def get_detail(self):
# .ui is set for single item selection.
index = self.users.selectedIndexes()[0]
item = self.user_model.data(index, Qt.UserRole)
self.log.debug("Generating detail window for {0}".format(item))
user_detail(self, item)
#
# Role criteria
#
def set_roles(self):
selected_roles = []
for index in self.roles.selectionModel().selectedIndexes():
selected_roles.append(self.role_model.data(index, Qt.UserRole))
self.query.roles = selected_roles
def invert_role_selection(self):
invert_list_selection(self.roles.selectionModel())
#
# Default level criteria
#
def clear_level_error(self):
self.level.setToolTip("Match the default level of the user.")
self.level.setPalette(self.orig_palette)
def set_level(self):
try:
self.query.level = self.level.text()
except Exception as ex:
self.log.info("Level criterion error: " + str(ex))
self.level.setToolTip("Error: " + str(ex))
self.level.setPalette(self.error_palette)
#
# Range criteria
#
def clear_range_error(self):
self.range_.setToolTip("Match the default range of the user.")
self.range_.setPalette(self.orig_palette)
def set_range(self):
try:
self.query.range_ = self.range_.text()
except Exception as ex:
self.log.info("Range criterion error: " + str(ex))
self.range_.setToolTip("Error: " + str(ex))
self.range_.setPalette(self.error_palette)
#
# Results runner
#
def run(self, button):
# right now there is only one button.
self.query.roles_equal = self.roles_equal.isChecked()
self.query.level_dom = self.level_dom.isChecked()
self.query.level_domby = self.level_domby.isChecked()
self.query.range_overlap = self.range_overlap.isChecked()
self.query.range_subset = self.range_subset.isChecked()
self.query.range_superset = self.range_superset.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.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.finished.emit()