1 # Copyright 2015, Tresys Technology, LLC 2 # 3 # This file is part of SETools. 4 # 5 # SETools is free software: you can redistribute it and/or modify 6 # it under the terms of the GNU Lesser General Public License as 7 # published by the Free Software Foundation, either version 2.1 of 8 # the License, or (at your option) any later version. 9 # 10 # SETools is distributed in the hope that it will be useful, 11 # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 # GNU Lesser General Public License for more details. 14 # 15 # You should have received a copy of the GNU Lesser General Public 16 # License along with SETools. If not, see 17 # <http://www.gnu.org/licenses/>. 18 # 19 20 import logging 21 22 from PyQt5.QtCore import Qt 23 from PyQt5.QtWidgets import QAction, QDialog, QFileDialog, QLineEdit, QMainWindow, QMenu, \ 24 QMessageBox, QTreeWidgetItem, QVBoxLayout, QWidget 25 from setools import PermissionMap, SELinuxPolicy 26 27 from ..widget import SEToolsWidget 28 from .terulequery import TERuleQueryTab 29 30 31 class ApolMainWindow(SEToolsWidget, QMainWindow): 32 33 def __init__(self, filename): 34 super(ApolMainWindow, self).__init__() 35 self.log = logging.getLogger(self.__class__.__name__) 36 37 if filename: 38 self._policy = SELinuxPolicy(filename) 39 else: 40 self._policy = None 41 42 try: 43 # try to load default permission map 44 self._permmap = PermissionMap() 45 except (IOError, OSError) as ex: 46 self.log.info("Failed to load default permission map: {0}".format(ex)) 47 self._permmap = None 48 49 self.setupUi() 50 51 def setupUi(self): 52 self.load_ui("apol.ui") 53 54 self.tab_counter = 0 55 56 self.update_window_title() 57 58 # set up error message dialog 59 self.error_msg = QMessageBox(self) 60 self.error_msg.setStandardButtons(QMessageBox.Ok) 61 62 # set up tab name editor 63 self.tab_editor = QLineEdit(self.AnalysisTabs) 64 self.tab_editor.setWindowFlags(Qt.Popup) 65 66 # configure tab bar context menu 67 tabBar = self.AnalysisTabs.tabBar() 68 self.rename_tab_action = QAction("&Rename active tab", tabBar) 69 self.close_tab_action = QAction("&Close active tab", tabBar) 70 tabBar.addAction(self.rename_tab_action) 71 tabBar.addAction(self.close_tab_action) 72 tabBar.setContextMenuPolicy(Qt.ActionsContextMenu) 73 74 # connect signals 75 self.open_policy.triggered.connect(self.select_policy) 76 self.open_permmap.triggered.connect(self.select_permmap) 77 self.new_analysis.triggered.connect(self.choose_analysis) 78 self.AnalysisTabs.tabCloseRequested.connect(self.close_tab) 79 self.AnalysisTabs.tabBarDoubleClicked.connect(self.tab_name_editor) 80 self.tab_editor.editingFinished.connect(self.rename_tab) 81 self.rename_tab_action.triggered.connect(self.rename_active_tab) 82 self.close_tab_action.triggered.connect(self.close_active_tab) 83 84 self.show() 85 86 def update_window_title(self): 87 if self._policy: 88 self.setWindowTitle("{0} - apol".format(self._policy)) 89 else: 90 self.setWindowTitle("apol") 91 92 def select_policy(self): 93 filename = QFileDialog.getOpenFileName(self, "Open policy file", ".")[0] 94 if filename: 95 try: 96 self._policy = SELinuxPolicy(filename) 97 except Exception as ex: 98 self.error_msg.critical(self, "Policy loading error", str(ex)) 99 else: 100 self.update_window_title() 101 102 if self._permmap: 103 self._permmap.map_policy(self._policy) 104 105 def select_permmap(self): 106 filename = QFileDialog.getOpenFileName(self, "Open permission map file", ".")[0] 107 if filename: 108 try: 109 self._permmap = PermissionMap(filename) 110 except Exception as ex: 111 self.error_msg.critical(self, "Permission map loading error", str(ex)) 112 else: 113 114 if self._policy: 115 self._permmap.map_policy(self._policy) 116 117 def choose_analysis(self): 118 if not self._policy: 119 self.error_msg.critical(self, "No open policy", 120 "Cannot start a new analysis. Please open a policy first.") 121 122 self.select_policy() 123 124 if self._policy: 125 # this check of self._policy is here in case someone 126 # tries to start an analysis with no policy open, but then 127 # cancels out of the policy file chooser or there is an 128 # error opening the policy file. 129 chooser = ChooseAnalysis(self) 130 chooser.show() 131 132 def create_new_analysis(self, tabtitle, tabclass): 133 self.tab_counter += 1 134 counted_name = "{0}: {1}".format(self.tab_counter, tabtitle) 135 136 newtab = QWidget() 137 newtab.setObjectName(counted_name) 138 139 newanalysis = tabclass(newtab, self._policy) 140 141 # create a vertical layout in the tab, place the analysis ui inside. 142 tabLayout = QVBoxLayout() 143 tabLayout.setContentsMargins(0, 0, 0, 0) 144 tabLayout.addWidget(newanalysis) 145 newtab.setLayout(tabLayout) 146 147 index = self.AnalysisTabs.addTab(newtab, counted_name) 148 self.AnalysisTabs.setTabToolTip(index, tabtitle) 149 150 def tab_name_editor(self, index): 151 if index >= 0: 152 tab_area = self.AnalysisTabs.tabBar().tabRect(index) 153 self.tab_editor.move(self.AnalysisTabs.mapToGlobal(tab_area.topLeft())) 154 self.tab_editor.setText(self.AnalysisTabs.tabText(index)) 155 self.tab_editor.selectAll() 156 self.tab_editor.show() 157 self.tab_editor.setFocus() 158 159 def close_active_tab(self): 160 index = self.AnalysisTabs.currentIndex() 161 if index >= 0: 162 self.close_tab(index) 163 164 def rename_active_tab(self): 165 index = self.AnalysisTabs.currentIndex() 166 if index >= 0: 167 self.tab_name_editor(index) 168 169 def close_tab(self, index): 170 widget = self.AnalysisTabs.widget(index) 171 widget.close() 172 widget.deleteLater() 173 self.AnalysisTabs.removeTab(index) 174 175 def rename_tab(self): 176 # this should never be negative since the editor is modal 177 index = self.AnalysisTabs.currentIndex() 178 179 self.tab_editor.hide() 180 self.AnalysisTabs.setTabText(index, self.tab_editor.text()) 181 182 183 class ChooseAnalysis(SEToolsWidget, QDialog): 184 185 """ 186 Dialog for choosing a new analysis 187 188 The below class attributes are used for populating 189 the GUI contents and mapping them to the appropriate 190 tab widget class for the analysis. 191 192 The item_mapping attribute will be populated to 193 map the tree list items to the analysis tab widgets. 194 """ 195 _components_map = {"Attributes (Type)": TERuleQueryTab, 196 "Booleans": TERuleQueryTab, 197 "Categories": TERuleQueryTab, 198 "Common Permission Sets": TERuleQueryTab, 199 "Object Classes": TERuleQueryTab, 200 "Policy Capabilities": TERuleQueryTab, 201 "Roles": TERuleQueryTab, 202 "Types": TERuleQueryTab, 203 "Users": TERuleQueryTab} 204 205 _rule_map = {"TE Rules": TERuleQueryTab, 206 "RBAC Rules": TERuleQueryTab, 207 "MLS Rules": TERuleQueryTab, 208 "Constraints": TERuleQueryTab} 209 210 _analysis_map = {"Domain Transition Analysis": TERuleQueryTab, 211 "Information Flow Analysis": TERuleQueryTab} 212 213 _labeling_map = {"fs_use Statements": TERuleQueryTab, 214 "Genfscon Statements": TERuleQueryTab, 215 "Initial SID Statements": TERuleQueryTab, 216 "Netifcon Statements": TERuleQueryTab, 217 "Nodecon Statements": TERuleQueryTab, 218 "Portcon Statements": TERuleQueryTab} 219 220 _analysis_choices = {"Components": _components_map, 221 "Rules": _rule_map, 222 "Analysis": _analysis_map, 223 "Labeling Statements": _labeling_map} 224 225 def __init__(self, parent): 226 super(ChooseAnalysis, self).__init__(parent) 227 self.item_mapping = {} 228 self.parent = parent 229 self.setupUi() 230 231 def setupUi(self): 232 self.load_ui("choose_analysis.ui") 233 self.buttonBox.accepted.connect(self.ok_clicked) 234 self.analysisTypes.doubleClicked.connect(self.ok_clicked) 235 236 # populate the item list: 237 self.analysisTypes.clear() 238 for groupname, group in self._analysis_choices.items(): 239 groupitem = QTreeWidgetItem(self.analysisTypes) 240 groupitem.setText(0, groupname) 241 groupitem._tab_class = None 242 for entryname, cls in group.items(): 243 item = QTreeWidgetItem(groupitem) 244 item.setText(0, entryname) 245 item._tab_class = cls 246 groupitem.addChild(item) 247 248 self.analysisTypes.expandAll() 249 250 def ok_clicked(self): 251 try: 252 # .ui is set for single item selection. 253 item = self.analysisTypes.selectedItems()[0] 254 title = item.text(0) 255 self.parent.create_new_analysis(title, item._tab_class) 256 except (IndexError, TypeError): 257 # IndexError: nothing is selected 258 # TypeError: one of the group items was selected. 259 pass 260 else: 261 self.accept() 262