Home | History | Annotate | Download | only in apol
      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