mirror of
https://github.com/SELinuxProject/setools
synced 2025-05-02 00:04:23 +00:00
305 lines
11 KiB
Python
305 lines
11 KiB
Python
# SPDX-License-Identifier: LGPL-2.1-only
|
|
# Copyright 2016, Tresys Technology, LLC
|
|
|
|
import copy
|
|
import logging
|
|
|
|
from PyQt6 import QtCore, QtGui, QtWidgets
|
|
from setools import PermissionMap
|
|
|
|
from . import models, views
|
|
|
|
|
|
class PermissionMapEditor(QtWidgets.QDialog):
|
|
|
|
"""
|
|
A permission map editor. This dialog has two versions,
|
|
one for editing the weight/direction and another for
|
|
including or excluding permissions in an analysis.
|
|
|
|
Parameters:
|
|
parent The parent Qt widget
|
|
edit (bool) If true, the dialog will take
|
|
the editor behavior. If False, the dialog
|
|
will take the enable/disable permission
|
|
behavior.
|
|
"""
|
|
|
|
apply_permmap = QtCore.pyqtSignal(PermissionMap)
|
|
class_toggle = QtCore.pyqtSignal(bool)
|
|
|
|
def __init__(self, perm_map: PermissionMap, edit: bool = False,
|
|
parent: QtWidgets.QWidget | None = None) -> None:
|
|
super().__init__(parent)
|
|
self.log = logging.getLogger(__name__)
|
|
self.edit = edit
|
|
|
|
# keep an internal copy because the map is mutable
|
|
# and this dialog may be canceled after some edits.
|
|
self.perm_map = copy.deepcopy(perm_map)
|
|
|
|
if self.edit:
|
|
self.setWindowTitle(f"{self.perm_map} - Permission Map Editor - apol")
|
|
else:
|
|
self.setWindowTitle(f"{self.perm_map} - Permission Map Viewer - apol")
|
|
|
|
top_layout = QtWidgets.QVBoxLayout(self)
|
|
|
|
#
|
|
# Title
|
|
#
|
|
title = QtWidgets.QLabel(self)
|
|
title.setObjectName("title")
|
|
top_layout.addWidget(title)
|
|
|
|
if self.edit:
|
|
title.setText("Permission Map Editor")
|
|
else:
|
|
title.setText("Permission Map Viewer")
|
|
|
|
frame = QtWidgets.QFrame(self)
|
|
frame.setFrameShape(QtWidgets.QFrame.Shape.StyledPanel)
|
|
frame.setFrameShadow(QtWidgets.QFrame.Shadow.Raised)
|
|
frame_layout = QtWidgets.QGridLayout(frame)
|
|
|
|
# set up class list
|
|
self.classes = views.SEToolsListView(frame)
|
|
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Maximum,
|
|
QtWidgets.QSizePolicy.Policy.Preferred)
|
|
sizePolicy.setHorizontalStretch(0)
|
|
sizePolicy.setVerticalStretch(0)
|
|
sizePolicy.setHeightForWidth(self.classes.sizePolicy().hasHeightForWidth())
|
|
self.classes.setSizePolicy(sizePolicy)
|
|
self.classes.setModel(models.StringList(data=sorted(self.perm_map.classes()), parent=self))
|
|
self.classes.selectionModel().selectionChanged.connect(self.class_selected)
|
|
frame_layout.addWidget(self.classes, 0, 1, 1, 1)
|
|
|
|
# Enable all button
|
|
self.enable_all = QtWidgets.QPushButton(frame)
|
|
self.enable_all.setText("Include All Permissions")
|
|
frame_layout.addWidget(self.enable_all, 1, 2, 1, 1)
|
|
|
|
# Disable all button
|
|
self.disable_all = QtWidgets.QPushButton(frame)
|
|
self.disable_all.setText("Exclude All Permissions")
|
|
frame_layout.addWidget(self.disable_all, 1, 3, 1, 1)
|
|
|
|
# permission widgets
|
|
self.widgets = list[PermissionMapping | QtWidgets.QFrame]()
|
|
scrollArea = QtWidgets.QScrollArea(frame)
|
|
scrollArea.setWidgetResizable(True)
|
|
scrollArea.setAlignment(
|
|
QtCore.Qt.AlignmentFlag.AlignLeft |
|
|
QtCore.Qt.AlignmentFlag.AlignTop)
|
|
scrollAreaWidgetContents = QtWidgets.QWidget()
|
|
scrollAreaWidgetContents.setGeometry(QtCore.QRect(0, 0, 463, 331))
|
|
self.perm_mappings = QtWidgets.QVBoxLayout(scrollAreaWidgetContents)
|
|
scrollArea.setWidget(scrollAreaWidgetContents)
|
|
frame_layout.addWidget(scrollArea, 0, 2, 1, 2)
|
|
top_layout.addWidget(frame)
|
|
|
|
self.buttonBox = QtWidgets.QDialogButtonBox(self)
|
|
self.buttonBox.setOrientation(QtCore.Qt.Orientation.Horizontal)
|
|
self.buttonBox.setStandardButtons(
|
|
QtWidgets.QDialogButtonBox.StandardButton.Cancel |
|
|
QtWidgets.QDialogButtonBox.StandardButton.Ok)
|
|
top_layout.addWidget(self.buttonBox)
|
|
|
|
# set up editor mode
|
|
self.enable_all.setHidden(self.edit)
|
|
self.disable_all.setHidden(self.edit)
|
|
|
|
# connect signals
|
|
self.enable_all.clicked.connect(self.enable_all_perms)
|
|
self.disable_all.clicked.connect(self.disable_all_perms)
|
|
self.buttonBox.accepted.connect(self.accept)
|
|
self.buttonBox.rejected.connect(self.reject)
|
|
QtCore.QMetaObject.connectSlotsByName(self)
|
|
|
|
def accept(self) -> None:
|
|
"""Accept the dialog and emit the perm_map signal."""
|
|
self.apply_permmap.emit(self.perm_map)
|
|
super().accept()
|
|
|
|
def class_selected(self) -> None:
|
|
"""Handle a class being selected."""
|
|
# the widget is set to 1 selection
|
|
selection_model = self.classes.selectionModel()
|
|
assert selection_model, "No selection model set, this is an SETools bug." # type narrowing
|
|
data_model = self.classes.model()
|
|
assert data_model, "No data model set, this is an SETools bug." # type narrowing
|
|
for index in selection_model.selectedIndexes():
|
|
class_name = data_model.data(index, QtCore.Qt.ItemDataRole.DisplayRole)
|
|
|
|
self.log.debug(f"Setting class to {class_name}")
|
|
|
|
self.enable_all.setToolTip(f"Include all permissions in the {class_name} class.")
|
|
self.disable_all.setToolTip(f"Exclude all permissions in the {class_name} class.")
|
|
|
|
self._clear_mappings()
|
|
|
|
# populate new mappings
|
|
for perm in sorted(self.perm_map.perms(class_name)):
|
|
# create permission mapping
|
|
mapping = PermissionMapping(perm, self.edit, self)
|
|
mapping.setAttribute(QtCore.Qt.WidgetAttribute.WA_DeleteOnClose)
|
|
self.class_toggle.connect(mapping.enabled.setChecked)
|
|
self.perm_mappings.addWidget(mapping)
|
|
self.widgets.append(mapping)
|
|
|
|
# add horizonal line
|
|
line = QtWidgets.QFrame(self)
|
|
line.setFrameShape(QtWidgets.QFrame.Shape.HLine)
|
|
line.setFrameShadow(QtWidgets.QFrame.Shadow.Sunken)
|
|
self.perm_mappings.addWidget(line)
|
|
self.widgets.append(line)
|
|
|
|
def enable_all_perms(self) -> None:
|
|
"""Enable all permissions in the current class."""
|
|
self.class_toggle.emit(True)
|
|
|
|
def disable_all_perms(self) -> None:
|
|
"""Disable all permissions in the current class."""
|
|
self.class_toggle.emit(False)
|
|
|
|
#
|
|
# Internal functions
|
|
#
|
|
def _clear_mappings(self):
|
|
# delete current mappings
|
|
for mapping in self.widgets:
|
|
mapping.close()
|
|
|
|
self.widgets.clear()
|
|
|
|
|
|
index_to_setting = ["r", "w", "b", "n"]
|
|
index_to_word = ["Read", "Write", "Both", "None"]
|
|
setting_to_index = {"r": 0, "w": 1, "b": 2, "n": 3}
|
|
|
|
|
|
class PermissionMapping(QtWidgets.QWidget):
|
|
|
|
"""
|
|
A widget representing mapping for a particular permission.
|
|
This dialog has two versions, one for editing the weight/direction
|
|
and another for including or excluding permissions in an analysis.
|
|
|
|
Parameters:
|
|
parent The parent Qt widget
|
|
edit (bool) If true, the widget will take
|
|
the editor behavior. If False, the dialog
|
|
will take the enable/disable permission
|
|
behavior.
|
|
"""
|
|
|
|
def __init__(self, mapping, edit: bool = False, parent: PermissionMapEditor | None = None):
|
|
super().__init__(parent)
|
|
self.log = logging.getLogger(__name__)
|
|
self.mapping = mapping
|
|
self.edit = edit
|
|
|
|
self.resize(457, 41)
|
|
self.horizontalLayout = QtWidgets.QHBoxLayout(self)
|
|
self.permission = QtWidgets.QLabel(self)
|
|
self.permission.setText(str(self.mapping.perm))
|
|
self.horizontalLayout.addWidget(self.permission)
|
|
self.direction = QtWidgets.QComboBox(self)
|
|
self.horizontalLayout.addWidget(self.direction)
|
|
self.weight = QtWidgets.QSpinBox(self)
|
|
self.weight.setMinimum(1)
|
|
self.weight.setMaximum(10)
|
|
self.weight.setSingleStep(1)
|
|
self.weight.setValue(self.mapping.weight)
|
|
self.horizontalLayout.addWidget(self.weight)
|
|
self.enabled = QtWidgets.QCheckBox(self)
|
|
self.enabled.setText("Include")
|
|
self.enabled.setChecked(self.mapping.enabled)
|
|
self.horizontalLayout.addWidget(self.enabled)
|
|
|
|
if self.edit:
|
|
self.weight.setToolTip(
|
|
f"Set the information flow weight of \"{self.mapping.perm}\"")
|
|
self.direction.setToolTip(
|
|
f"Set the information flow direction of \"{self.mapping.perm}\"")
|
|
else:
|
|
self.enabled.setToolTip(
|
|
f"Include or exclude \"{self.mapping.perm}\" from the analysis.")
|
|
|
|
self.weight.setEnabled(self.edit)
|
|
self.direction.setEnabled(self.edit)
|
|
self.enabled.setHidden(self.edit)
|
|
|
|
# setup color palettes for direction
|
|
self.orig_palette = self.direction.palette()
|
|
self.error_palette = self.direction.palette()
|
|
self.error_palette.setColor(QtGui.QPalette.ColorRole.Button,
|
|
QtCore.Qt.GlobalColor.red)
|
|
self.error_palette.setColor(QtGui.QPalette.ColorRole.ButtonText,
|
|
QtCore.Qt.GlobalColor.white)
|
|
|
|
# setup direction
|
|
self.direction.insertItems(0, index_to_word)
|
|
if self.mapping.direction == 'u':
|
|
# Temporarily add unmapped value to items
|
|
self.direction.insertItem(len(index_to_word), "Unmapped")
|
|
self.direction.setCurrentText("Unmapped")
|
|
self.direction.setPalette(self.error_palette)
|
|
self.unmapped = True
|
|
else:
|
|
self.direction.setCurrentIndex(setting_to_index[self.mapping.direction])
|
|
self.unmapped = False
|
|
|
|
# connect signals
|
|
self.direction.currentIndexChanged.connect(self.set_direction)
|
|
self.weight.valueChanged.connect(self.set_weight)
|
|
self.enabled.toggled.connect(self.set_enabled)
|
|
QtCore.QMetaObject.connectSlotsByName(self)
|
|
|
|
def set_direction(self, value) -> None:
|
|
"""Set the direction for the mapping."""
|
|
if self.unmapped:
|
|
if value == "Unmapped":
|
|
return
|
|
|
|
# Remove unmapped item if setting the mapping.
|
|
self.direction.removeItem(len(index_to_word))
|
|
self.direction.setPalette(self.orig_palette)
|
|
self.unmapped = False
|
|
|
|
dir_ = index_to_setting[value]
|
|
self.log.debug(f"Setting {self.mapping.class_}:{self.mapping.perm} direction to {dir_}")
|
|
self.mapping.direction = dir_
|
|
|
|
def set_weight(self, value: str | int) -> None:
|
|
"""Set the weight for the mapping."""
|
|
self.log.debug(f"Setting {self.mapping.class_}:{self.mapping.perm} weight to {value}")
|
|
self.mapping.weight = int(value)
|
|
|
|
def set_enabled(self, value: bool) -> None:
|
|
"""Set the enabled value for the mapping."""
|
|
self.log.debug(f"Setting {self.mapping.class_}:{self.mapping.perm} enabled to {value}")
|
|
self.mapping.enabled = value
|
|
|
|
|
|
if __name__ == '__main__':
|
|
import sys
|
|
import warnings
|
|
import setools
|
|
|
|
logging.basicConfig(level=logging.DEBUG,
|
|
format='%(asctime)s|%(levelname)s|%(name)s|%(message)s')
|
|
warnings.simplefilter("default")
|
|
|
|
app = QtWidgets.QApplication(sys.argv)
|
|
p = setools.SELinuxPolicy()
|
|
m = setools.PermissionMap()
|
|
m.map_policy(p)
|
|
pview = PermissionMapEditor(m, edit=False)
|
|
ped = PermissionMapEditor(m, edit=True)
|
|
pview.show()
|
|
ped.show()
|
|
rc = app.exec()
|
|
|
|
sys.exit(rc)
|