├── .github └── workflows │ ├── stale.yml │ └── tests.yml ├── .gitignore ├── COPYING ├── COPYING.GPL ├── COPYING.LGPL ├── ChangeLog ├── KNOWN-BUGS ├── MANIFEST.in ├── README.md ├── apol ├── man ├── apol.1 ├── ru │ ├── apol.1 │ ├── sediff.1 │ ├── sedta.1 │ ├── seinfo.1 │ ├── seinfoflow.1 │ └── sesearch.1 ├── sechecker.1 ├── sediff.1 ├── sedta.1 ├── seinfo.1 ├── seinfoflow.1 └── sesearch.1 ├── patches └── README ├── pyproject.toml ├── sechecker ├── sediff ├── sedta ├── seinfo ├── seinfoflow ├── sesearch ├── setools ├── __init__.py ├── boolquery.py ├── boundsquery.py ├── categoryquery.py ├── checker │ ├── __init__.py │ ├── assertrbac.py │ ├── assertte.py │ ├── checker.py │ ├── checkermodule.py │ ├── descriptors.py │ ├── emptyattr.py │ ├── globalkeys.py │ ├── roexec.py │ ├── rokmod.py │ └── util.py ├── commonquery.py ├── constraintquery.py ├── defaultquery.py ├── descriptors.py ├── devicetreeconquery.py ├── diff │ ├── __init__.py │ ├── bool.py │ ├── bounds.py │ ├── commons.py │ ├── conditional.py │ ├── constraints.py │ ├── context.py │ ├── default.py │ ├── descriptors.py │ ├── difference.py │ ├── fsuse.py │ ├── genfscon.py │ ├── ibendportcon.py │ ├── ibpkeycon.py │ ├── initsid.py │ ├── mls.py │ ├── mlsrules.py │ ├── netifcon.py │ ├── nodecon.py │ ├── objclass.py │ ├── polcap.py │ ├── portcon.py │ ├── properties.py │ ├── rbacrules.py │ ├── roles.py │ ├── terules.py │ ├── typeattr.py │ ├── types.py │ ├── typing.py │ └── users.py ├── dta.py ├── exception.py ├── fsusequery.py ├── genfsconquery.py ├── ibendportconquery.py ├── ibpkeyconquery.py ├── infoflow.py ├── initsidquery.py ├── iomemconquery.py ├── ioportconquery.py ├── mixins.py ├── mlsrulequery.py ├── netifconquery.py ├── nodeconquery.py ├── objclassquery.py ├── pcideviceconquery.py ├── perm_map ├── permmap.py ├── pirqconquery.py ├── polcapquery.py ├── policyrep.pyi ├── policyrep.pyx ├── policyrep │ ├── boolcond.pxi │ ├── bounds.pxi │ ├── constraint.pxi │ ├── context.pxi │ ├── default.pxi │ ├── fscontext.pxi │ ├── initsid.pxi │ ├── mls.pxi │ ├── mlsrule.pxi │ ├── netcontext.pxi │ ├── objclass.pxi │ ├── object.pxi │ ├── polcap.pxi │ ├── rbacrule.pxi │ ├── role.pxi │ ├── rule.pxi │ ├── selinux.pxd │ ├── selinuxpolicy.pxi │ ├── sepol.pxd │ ├── terule.pxi │ ├── typeattr.pxi │ ├── user.pxi │ ├── util.pxi │ └── xencontext.pxi ├── portconquery.py ├── py.typed ├── query.py ├── rbacrulequery.py ├── rolequery.py ├── roletypesquery.py ├── sensitivityquery.py ├── terulequery.py ├── typeattrquery.py ├── typequery.py ├── userquery.py └── util.py ├── setoolsgui ├── __init__.py ├── apol.css ├── apol.html ├── apol.py ├── config.py └── widgets │ ├── __init__.py │ ├── boolquery.py │ ├── boundsquery.py │ ├── categoryquery.py │ ├── commonquery.py │ ├── constraintquery.py │ ├── criteria │ ├── __init__.py │ ├── boolean.py │ ├── boundsruletype.py │ ├── checkboxset.py │ ├── combobox.py │ ├── comboenum.py │ ├── common.py │ ├── constraintype.py │ ├── context.py │ ├── criteria.py │ ├── defaultruletype.py │ ├── defaultvalue.py │ ├── fsuseruletype.py │ ├── infiniband.py │ ├── ipnetwork.py │ ├── ipports.py │ ├── list.py │ ├── mls.py │ ├── mlslevelrange.py │ ├── mlsruletype.py │ ├── name.py │ ├── objclass.py │ ├── permission.py │ ├── radioenum.py │ ├── ranged.py │ ├── rbacruletype.py │ ├── role.py │ ├── teruletype.py │ ├── type.py │ ├── typeattr.py │ └── user.py │ ├── defaultquery.py │ ├── details │ ├── __init__.py │ ├── boolean.py │ ├── common.py │ ├── context.py │ ├── objclass.py │ ├── role.py │ ├── type.py │ ├── typeattr.py │ ├── user.py │ └── util.py │ ├── dta.html │ ├── dta.py │ ├── exception.py │ ├── excludetypes.py │ ├── fsusequery.py │ ├── genfsconquery.py │ ├── helpdialog.py │ ├── ibendportconquery.py │ ├── ibpkeyconquery.py │ ├── infoflow.html │ ├── infoflow.py │ ├── initsidquery.py │ ├── mlsrulequery.py │ ├── models │ ├── __init__.py │ ├── boolean.py │ ├── bounds.py │ ├── common.py │ ├── constraint.py │ ├── default.py │ ├── fsuse.py │ ├── genfscon.py │ ├── ibendportcon.py │ ├── ibpkeycon.py │ ├── initsid.py │ ├── mls.py │ ├── mlsrule.py │ ├── modelroles.py │ ├── netifcon.py │ ├── nodecon.py │ ├── objclass.py │ ├── portcon.py │ ├── rbacrule.py │ ├── role.py │ ├── table.py │ ├── terule.py │ ├── type.py │ ├── typeattr.py │ ├── typing.py │ └── user.py │ ├── netifconquery.py │ ├── nodeconquery.py │ ├── objclassquery.py │ ├── permmap.py │ ├── portconquery.py │ ├── queryupdater.py │ ├── rbacrulequery.py │ ├── rolequery.py │ ├── sensitivityquery.py │ ├── summary.py │ ├── tab.py │ ├── terulequery.py │ ├── typeattrquery.py │ ├── typequery.py │ ├── userquery.py │ ├── util.py │ └── views │ ├── __init__.py │ ├── listview.py │ ├── tableview.py │ └── treewidget.py ├── setup.py ├── tests ├── __init__.py ├── conftest.py ├── gui │ ├── __init__.py │ ├── conftest.py │ └── widgets │ │ ├── __init__.py │ │ ├── criteria │ │ ├── __init__.py │ │ ├── test_boolean.py │ │ ├── test_checkboxset.py │ │ ├── test_common.py │ │ ├── test_list.py │ │ ├── test_name.py │ │ ├── test_objclass.py │ │ ├── test_permission.py │ │ ├── test_rbacruletype.py │ │ ├── test_role.py │ │ ├── test_teruletype.py │ │ ├── test_type.py │ │ └── test_user.py │ │ ├── test_boolquery.py │ │ ├── test_boundsquery.py │ │ ├── test_categoryquery.py │ │ ├── test_commonquery.py │ │ ├── test_constraintquery.py │ │ ├── test_defaultquery.py │ │ ├── test_fsusequery.py │ │ ├── test_genfsconquery.py │ │ ├── test_ibendportconquery.py │ │ ├── test_ibpkeyconquery.py │ │ ├── test_initialsidquery.py │ │ ├── test_mlsrulequery.py │ │ ├── test_netifconquery.py │ │ ├── test_nodeconquery.py │ │ ├── test_objclassquery.py │ │ ├── test_portconquery.py │ │ ├── test_rbacrulequery.py │ │ ├── test_rolequery.py │ │ ├── test_sensitivityquery.py │ │ ├── test_tab.py │ │ ├── test_terulequery.py │ │ ├── test_typeattrquery.py │ │ ├── test_typequery.py │ │ └── test_userquery.py └── library │ ├── __init__.py │ ├── boolquery.conf │ ├── boundsquery.conf │ ├── categoryquery.conf │ ├── checker │ ├── __init__.py │ ├── assertrbac.conf │ ├── assertte.conf │ ├── checker-invalidoption.ini │ ├── checker-invalidtype.ini │ ├── checker-invalidvalue.ini │ ├── checker-missingtype.ini │ ├── checker-valid.ini │ ├── checker.conf │ ├── emptyattr.conf │ ├── roexec.conf │ ├── rokmod.conf │ ├── test_assertrbac.py │ ├── test_assertte.py │ ├── test_checker.py │ ├── test_emptyattr.py │ ├── test_roexec.py │ ├── test_rokmod.py │ └── util.py │ ├── commonquery.conf │ ├── conditionalinfoflow.conf │ ├── constraintquery.conf │ ├── defaultquery.conf │ ├── devicetreeconquery.conf │ ├── diff_left.conf │ ├── diff_left_redundant.conf │ ├── diff_left_standard.conf │ ├── diff_right.conf │ ├── diff_right_rmisid.conf │ ├── dta.conf │ ├── fsusequery.conf │ ├── genfsconquery.conf │ ├── ibendportconquery.conf │ ├── ibpkeyconquery.conf │ ├── infoflow.conf │ ├── initsidquery.conf │ ├── invalid_perm_maps │ ├── bad-class-keyword │ ├── bad-perm-weight-high │ ├── bad-perm-weight-low │ ├── bad-permcount │ ├── extra-class │ ├── extra-perms │ ├── invalid-flowdir │ ├── invalid-perm-weight │ ├── negative-classcount │ ├── negative-permcount │ └── non-number-classcount │ ├── iomemconquery.conf │ ├── ioportconquery.conf │ ├── mlsrulequery.conf │ ├── netifconquery.conf │ ├── nodeconquery.conf │ ├── objclassquery.conf │ ├── pcideviceconquery.conf │ ├── perm_map │ ├── permmap.conf │ ├── pirqconquery.conf │ ├── polcapquery.conf │ ├── policyrep │ ├── __init__.py │ ├── common.conf │ ├── default.conf │ ├── initsid.conf │ ├── invalid_policies │ │ ├── nodecon-invalid-range.conf │ │ └── user-level-not-in-range.conf │ ├── mls.conf │ ├── objclass.conf │ ├── polcap.conf │ ├── role.conf │ ├── rules.conf │ ├── selinuxpolicy.conf │ ├── terule_issue74.conf │ ├── test_common.py │ ├── test_default.py │ ├── test_initsid.py │ ├── test_mls.py │ ├── test_objclass.py │ ├── test_polcap.py │ ├── test_role.py │ ├── test_rules.py │ ├── test_selinuxpolicy.py │ ├── test_type.py │ ├── test_typeattr.py │ ├── test_user.py │ ├── type.conf │ ├── typeattr.conf │ ├── user_mls.conf │ └── user_standard.conf │ ├── portconquery.conf │ ├── rbacrulequery.conf │ ├── rolequery.conf │ ├── roletypesquery.conf │ ├── sensitivityquery.conf │ ├── terulequery.conf │ ├── terulequery2.conf │ ├── test_boolquery.py │ ├── test_boundsquery.py │ ├── test_categoryquery.py │ ├── test_commonquery.py │ ├── test_conditionalinfoflow.py │ ├── test_constraintquery.py │ ├── test_defaultquery.py │ ├── test_devicetreeconquery.py │ ├── test_diff.py │ ├── test_dta.py │ ├── test_fsusequery.py │ ├── test_genfsconquery.py │ ├── test_ibendportconquery.py │ ├── test_ibpkeyconquery.py │ ├── test_infoflow.py │ ├── test_initsidquery.py │ ├── test_iomemconquery.py │ ├── test_ioportconquery.py │ ├── test_mlsrulequery.py │ ├── test_netifconquery.py │ ├── test_nodeconquery.py │ ├── test_objclassquery.py │ ├── test_pcideviceconquery.py │ ├── test_permmap.py │ ├── test_pirqconquery.py │ ├── test_polcapquery.py │ ├── test_portconquery.py │ ├── test_rbacrulequery.py │ ├── test_rolequery.py │ ├── test_roletypesquery.py │ ├── test_sensitivityquery.py │ ├── test_terulequery.py │ ├── test_typeattrquery.py │ ├── test_typequery.py │ ├── test_userquery.py │ ├── typeattrquery.conf │ ├── typequery.conf │ ├── userquery.conf │ └── util.py └── tox.ini /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | name: Mark stale issues and pull requests 2 | 3 | on: 4 | schedule: 5 | - cron: "30 1 * * *" 6 | 7 | jobs: 8 | stale: 9 | 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/stale@v3 14 | with: 15 | repo-token: ${{ secrets.GITHUB_TOKEN }} 16 | stale-issue-message: 'This issue has not had any recent activity. It will be closed in 7 days if it makes no further progress.' 17 | close-issue-message: 'Closing stale PR.' 18 | stale-pr-message: 'This PR has not had any recent activity. It will be closed in 7 days if it makes no further progress.' 19 | close-pr-message: 'Closing stale PR.' 20 | stale-issue-label: 'stale' 21 | stale-pr-label: 'stale' 22 | exempt-issue-labels: 'question,help wanted' 23 | exempt-pr-labels: 'question,external bug' 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.eggs 2 | /.idea 3 | /.mypy_cache 4 | /.vscode 5 | /build 6 | /dist 7 | /setools.egg-info 8 | /venv 9 | *.pyc 10 | *.pyo 11 | /setools/policyrep.c 12 | *.so 13 | 14 | # Qt Generated Help files 15 | qhc/apol.qch 16 | qhc/apol.qhc 17 | 18 | # Generated by tox 19 | /.tox 20 | 21 | # Generated by coverage 22 | /htmlcov 23 | /.coverage 24 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | SETools libraries are provided under: 2 | 3 | SPDX-License-Identifier: LGPL-2.1-only 4 | 5 | Being under the terms of the GNU Lesser General Public License version 2.1 6 | only, according with: 7 | 8 | COPYING.LGPL 9 | 10 | 11 | SETools applications and unit tests are provided under: 12 | 13 | SPDX-License-Identifier: GPL-2.0-only 14 | 15 | Being under the terms of the GNU General Public License version 2 only, 16 | according with: 17 | 18 | COPYING.GPL 19 | 20 | 21 | All contributions to the SETools are subject to this COPYING file. 22 | -------------------------------------------------------------------------------- /KNOWN-BUGS: -------------------------------------------------------------------------------- 1 | The following is a list of known bugs with SETools. 2 | 3 | * A bug in NetworkX 1.8+ will output the following message to stderr if a type 4 | is valid, but not a node in a domain transition or information flow graph: 5 | 6 | 'Type' object is not iterable 7 | 8 | This message may be seen multiple times at the end of running unit tests. 9 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include ChangeLog 2 | include COPYING* 3 | include man/* 4 | include qhc/* 5 | include setools/perm_map 6 | include setools/policyrep/*.pxd 7 | include setools/policyrep/*.pxi 8 | include setools/*.pyx 9 | include setoolsgui/*.ui 10 | include setoolsgui/apol/*.ui 11 | include setoolsgui/apol/apol.qhc 12 | include tests/*.conf 13 | include tests/*.py 14 | include tests/perm_map 15 | -------------------------------------------------------------------------------- /apol: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright 2015, Tresys Technology, LLC 3 | # 4 | # SPDX-License-Identifier: GPL-2.0-only 5 | # 6 | 7 | import sys 8 | import argparse 9 | import logging 10 | import warnings 11 | 12 | import setools 13 | import setoolsgui 14 | 15 | parser = argparse.ArgumentParser(description="Graphical SELinux policy analysis tool.") 16 | parser.add_argument("--version", action="version", version=setools.__version__) 17 | parser.add_argument("policy", nargs="?", 18 | help="Path to the SELinux policy to analyze.") 19 | parser.add_argument("-v", "--verbose", action="store_true", 20 | help="Print extra informational messages") 21 | parser.add_argument("--debug", action="store_true", dest="debug", help="Enable debugging.") 22 | 23 | args = parser.parse_args() 24 | 25 | logging.basicConfig(level=logging.DEBUG, filename="/dev/null") 26 | 27 | console_handler = logging.StreamHandler() 28 | 29 | if args.debug: 30 | console_handler.setLevel(logging.DEBUG) 31 | console_handler.setFormatter( 32 | logging.Formatter('%(asctime)s|%(levelname)s|%(name)s|%(message)s')) 33 | 34 | if not sys.warnoptions: 35 | warnings.simplefilter("default") 36 | 37 | elif args.verbose: 38 | console_handler.setLevel(logging.INFO) 39 | console_handler.setFormatter(logging.Formatter('%(message)s')) 40 | 41 | if not sys.warnoptions: 42 | warnings.simplefilter("default") 43 | else: 44 | console_handler.setLevel(logging.WARNING) 45 | console_handler.setFormatter(logging.Formatter('%(message)s')) 46 | 47 | if not sys.warnoptions: 48 | warnings.simplefilter("ignore") 49 | 50 | logging.getLogger().addHandler(console_handler) 51 | 52 | try: 53 | sys.exit(setoolsgui.run_apol(args.policy)) 54 | 55 | except AssertionError: 56 | # Always provide a traceback for assertion errors 57 | raise 58 | 59 | except Exception as err: 60 | if args.debug: 61 | raise 62 | else: 63 | print(err) 64 | 65 | sys.exit(1) 66 | -------------------------------------------------------------------------------- /man/apol.1: -------------------------------------------------------------------------------- 1 | .\" Copyright (c) 2016 Tresys Technology, LLC. All rights reserved. 2 | .TH apol 1 2016-02-20 "SELinux Project" "SETools: SELinux Policy Analysis Tools" 3 | 4 | .SH NAME 5 | apol \- Graphical SELinux policy analysis tool 6 | 7 | .SH SYNOPSIS 8 | \fBapol\fR [OPTIONS] [POLICY] 9 | 10 | .SH DESCRIPTION 11 | .PP 12 | \fBapol\fR is a graphical tool that allows the user to inspect and analyze aspects of an SELinux policy. 13 | 14 | .SH POLICY 15 | .PP 16 | A single file containing a binary policy. This file is usually named by version on Linux systems, for example, \fIpolicy.30\fR. This file is usually named \fIsepolicy\fR on Android systems. 17 | If not provided, \fBapol\fR will start with none loaded. 18 | 19 | .SH OPTIONS 20 | .IP "-h, --help" 21 | Print help information and exit. 22 | .IP "--version" 23 | Print version information and exit. 24 | .IP "-v, --verbose" 25 | Print additional informational messages. 26 | .IP "--debug" 27 | Enable debugging output. 28 | 29 | .SH AUTHOR 30 | Chris PeBenito 31 | 32 | .SH BUGS 33 | Please report bugs via the SETools bug tracker, https://github.com/SELinuxProject/setools/issues 34 | 35 | .SH SEE ALSO 36 | sediff(1), sedta(1), seinfo(1), seinfoflow(1), sesearch(1) 37 | -------------------------------------------------------------------------------- /man/ru/apol.1: -------------------------------------------------------------------------------- 1 | .\" Copyright (c) 2016 Tresys Technology, LLC. All rights reserved. 2 | .TH apol 1 2016-02-20 "SELinux Project" "SETools: утилиты анализа политики SELinux" 3 | 4 | .SH ИМЯ 5 | apol \- графическая утилита анализа политики SELinux 6 | 7 | .SH ОБЗОР 8 | \fBapol\fR [OPTIONS] [POLICY] 9 | 10 | .SH ОПИСАНИЕ 11 | .PP 12 | \fBapol\fR - графическая утилита, которая позволяет пользователю изучить и проанализировать аспекты политики SELinux. 13 | 14 | .SH ПОЛИТИКА 15 | .PP 16 | \fBapol\fR поддерживает загрузку политик SELinux в одном из двух форматов. 17 | .RS 18 | .IP "source:" 19 | Один текстовый файл, содержащий источник монолитной политики. Этот файл обычно называется policy.conf. 20 | .IP "binary:" 21 | Один файл, содержащий двоичную политику. В системах Linux название этого файла обычно соответствует версии, например, \fIpolicy.30\fR. В системах Android этот файл обычно называется \fIsepolicy\fR. 22 | .RE 23 | .PP 24 | Если файл политики не указан, она не будет загружена при запуске \fBapol\fR. 25 | 26 | .SH ПАРАМЕТРЫ 27 | .IP "-h, --help" 28 | Вывести справочные сведения и выйти. 29 | .IP "--version" 30 | Вывести сведения о версии и выйти. 31 | .IP "-v, --verbose" 32 | Вывести дополнительные информационные сообщения. 33 | .IP "--debug" 34 | Включить отладочный вывод. 35 | 36 | .SH ОШИБКИ 37 | Пожалуйста, сообщайте об ошибках через систему отслеживания ошибок SETools, https://github.com/SELinuxProject/setools/issues 38 | 39 | .SH СМОТРИТЕ ТАКЖЕ 40 | sediff(1), sedta(1), seinfo(1), seinfoflow(1), sesearch(1) 41 | 42 | .SH АВТОРЫ 43 | Chris PeBenito . Перевод на русский язык выполнила Герасименко Олеся . 44 | -------------------------------------------------------------------------------- /patches/README: -------------------------------------------------------------------------------- 1 | If there is a bug in one of SETools' dependencies, patches can be put here. 2 | -------------------------------------------------------------------------------- /sechecker: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright 2020 Microsoft Corporation 3 | # 4 | # SPDX-License-Identifier: GPL-2.0-only 5 | # 6 | 7 | import setools 8 | import argparse 9 | import sys 10 | import logging 11 | import signal 12 | import warnings 13 | 14 | signal.signal(signal.SIGPIPE, signal.SIG_DFL) 15 | 16 | parser = argparse.ArgumentParser(description="SELinux policy checker tool.") 17 | parser.add_argument("--version", action="version", version=setools.__version__) 18 | parser.add_argument("config", help="Path to the checker configuration file.") 19 | parser.add_argument("policy", help="Path to the SELinux policy to check.", nargs="?") 20 | parser.add_argument("-o", "--output_file", help="Path to log output.", required=False) 21 | parser.add_argument("-v", "--verbose", action="store_true", 22 | help="Print extra informational messages") 23 | parser.add_argument("--debug", action="store_true", dest="debug", help="Enable debugging.") 24 | 25 | args = parser.parse_args() 26 | 27 | if args.debug: 28 | logging.basicConfig(level=logging.DEBUG, 29 | format='%(asctime)s|%(levelname)s|%(name)s|%(message)s') 30 | if not sys.warnoptions: 31 | warnings.simplefilter("default") 32 | elif args.verbose: 33 | logging.basicConfig(level=logging.INFO, format='%(message)s') 34 | if not sys.warnoptions: 35 | warnings.simplefilter("default") 36 | else: 37 | logging.basicConfig(level=logging.WARNING, format='%(message)s') 38 | if not sys.warnoptions: 39 | warnings.simplefilter("ignore") 40 | 41 | try: 42 | p = setools.SELinuxPolicy(args.policy) 43 | c = setools.PolicyChecker(p, args.config) 44 | 45 | if args.output_file: 46 | with open(args.output_file, "w", encoding="utf-8") as fd: 47 | failures = c.run(output=fd) 48 | 49 | else: 50 | failures = c.run() 51 | 52 | sys.exit(1 if failures else 0) 53 | 54 | except setools.exception.InvalidCheckerConfig as err: 55 | if args.debug: 56 | raise 57 | else: 58 | print(err) 59 | 60 | sys.exit(2) 61 | 62 | except AssertionError: 63 | # Always provide a traceback for assertion errors 64 | raise 65 | 66 | except Exception as err: 67 | if args.debug: 68 | raise 69 | else: 70 | print(err) 71 | 72 | sys.exit(3) 73 | -------------------------------------------------------------------------------- /setools/boolquery.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014-2015, Tresys Technology, LLC 2 | # 3 | # SPDX-License-Identifier: LGPL-2.1-only 4 | # 5 | from collections.abc import Iterable 6 | import typing 7 | 8 | from . import mixins, policyrep, query 9 | 10 | __all__: typing.Final[tuple[str, ...]] = ("BoolQuery",) 11 | 12 | 13 | class BoolQuery(mixins.MatchName, query.PolicyQuery): 14 | 15 | """Query SELinux policy Booleans. 16 | 17 | Parameter: 18 | policy The policy to query. 19 | 20 | Keyword Parameters/Class attributes: 21 | name The Boolean name to match. 22 | name_regex If true, regular expression matching 23 | will be used on the Boolean name. 24 | default The default state to match. If this 25 | is None, the default state not be matched. 26 | """ 27 | 28 | _default: bool | None = None 29 | 30 | @property 31 | def default(self) -> bool | None: 32 | return self._default 33 | 34 | @default.setter 35 | def default(self, value) -> None: 36 | if value is None: 37 | self._default = None 38 | else: 39 | self._default = bool(value) 40 | 41 | def results(self) -> Iterable[policyrep.Boolean]: 42 | """Generator which yields all Booleans matching the criteria.""" 43 | self.log.info(f"Generating Boolean results from {self.policy}") 44 | self._match_name_debug(self.log) 45 | self.log.debug(f"{self.default=}") 46 | 47 | for boolean in self.policy.bools(): 48 | if not self._match_name(boolean): 49 | continue 50 | 51 | if self.default is not None and boolean.state != self.default: 52 | continue 53 | 54 | yield boolean 55 | -------------------------------------------------------------------------------- /setools/boundsquery.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016, Tresys Technology, LLC 2 | # 3 | # SPDX-License-Identifier: LGPL-2.1-only 4 | # 5 | from collections.abc import Iterable 6 | import typing 7 | 8 | from . import policyrep, query, util 9 | from .descriptors import CriteriaDescriptor, CriteriaSetDescriptor 10 | 11 | __all__: typing.Final[tuple[str, ...]] = ("BoundsQuery",) 12 | 13 | 14 | class BoundsQuery(query.PolicyQuery): 15 | 16 | """ 17 | Query *bounds statements. 18 | 19 | Parameter: 20 | policy The policy to query. 21 | 22 | Keyword Parameters/Class attributes: 23 | ruletype The rule type(s) to match. 24 | """ 25 | 26 | ruletype = CriteriaSetDescriptor[policyrep.BoundsRuletype](enum_class=policyrep.BoundsRuletype) 27 | parent = CriteriaDescriptor[policyrep.Type]("parent_regex") 28 | parent_regex: bool = False 29 | child = CriteriaDescriptor[policyrep.Type]("child_regex") 30 | child_regex: bool = False 31 | 32 | def results(self) -> Iterable[policyrep.Bounds]: 33 | """Generator which yields all matching *bounds statements.""" 34 | self.log.info(f"Generating bounds results from {self.policy}") 35 | self.log.debug(f"{self.ruletype=}") 36 | self.log.debug(f"{self.parent=}, {self.parent_regex=}") 37 | self.log.debug(f"{self.child=}, {self.child_regex=}") 38 | 39 | for b in self.policy.bounds(): 40 | if self.ruletype and b.ruletype not in self.ruletype: 41 | continue 42 | 43 | if self.parent and not util.match_regex( 44 | b.parent, 45 | self.parent, 46 | self.parent_regex): 47 | continue 48 | 49 | if self.child and not util.match_regex( 50 | b.child, 51 | self.child, 52 | self.child_regex): 53 | continue 54 | 55 | yield b 56 | -------------------------------------------------------------------------------- /setools/categoryquery.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015, Tresys Technology, LLC 2 | # 3 | # SPDX-License-Identifier: LGPL-2.1-only 4 | # 5 | from collections.abc import Iterable 6 | import typing 7 | 8 | from . import mixins, policyrep, query 9 | 10 | __all__: typing.Final[tuple[str, ...]] = ("CategoryQuery",) 11 | 12 | 13 | class CategoryQuery(mixins.MatchAlias, mixins.MatchName, query.PolicyQuery): 14 | 15 | """ 16 | Query MLS Categories 17 | 18 | Parameter: 19 | policy The policy to query. 20 | 21 | Keyword Parameters/Class attributes: 22 | name The name of the category to match. 23 | name_regex If true, regular expression matching will 24 | be used for matching the name. 25 | alias The alias name to match. 26 | alias_regex If true, regular expression matching 27 | will be used on the alias names. 28 | """ 29 | 30 | def results(self) -> Iterable[policyrep.Category]: 31 | """Generator which yields all matching categories.""" 32 | self.log.info(f"Generating category results from {self.policy}") 33 | self._match_name_debug(self.log) 34 | self._match_alias_debug(self.log) 35 | 36 | for cat in self.policy.categories(): 37 | if not self._match_name(cat): 38 | continue 39 | 40 | if not self._match_alias(cat): 41 | continue 42 | 43 | yield cat 44 | -------------------------------------------------------------------------------- /setools/checker/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2020, Microsoft Corporation 2 | # 3 | # SPDX-License-Identifier: LGPL-2.1-only 4 | # 5 | 6 | from . import assertrbac 7 | from . import assertte 8 | from . import emptyattr 9 | from . import roexec 10 | from . import rokmod 11 | 12 | from .checker import PolicyChecker 13 | -------------------------------------------------------------------------------- /setools/checker/globalkeys.py: -------------------------------------------------------------------------------- 1 | # Copyright 2020, Microsoft Corporation 2 | # 3 | # SPDX-License-Identifier: LGPL-2.1-only 4 | # 5 | 6 | import typing 7 | 8 | # This is a separate file to break a circular import. 9 | CHECK_TYPE_KEY: typing.Final[str] = "check_type" 10 | CHECK_DESC_KEY: typing.Final[str] = "desc" 11 | CHECK_DISABLE: typing.Final[str] = "disable" 12 | 13 | GLOBAL_CONFIG_KEYS: typing.Final[frozenset[str]] = frozenset((CHECK_TYPE_KEY, 14 | CHECK_DESC_KEY, 15 | CHECK_DISABLE)) 16 | 17 | __all__: typing.Final[tuple[str, ...]] = ("CHECK_TYPE_KEY", 18 | "CHECK_DESC_KEY", 19 | "CHECK_DISABLE", 20 | "GLOBAL_CONFIG_KEYS") 21 | -------------------------------------------------------------------------------- /setools/checker/util.py: -------------------------------------------------------------------------------- 1 | # Copyright 2020, Microsoft Corporation 2 | # 3 | # SPDX-License-Identifier: LGPL-2.1-only 4 | # 5 | 6 | import typing 7 | 8 | __all__: typing.Final[tuple[str, ...]] = ('config_bool_value',) 9 | 10 | 11 | def config_bool_value(value) -> bool: 12 | """Convert a boolean configuration value.""" 13 | 14 | if isinstance(value, str): 15 | if value and value.strip().lower() in ("yes", "true", "1"): 16 | return True 17 | 18 | return False 19 | 20 | return bool(value) 21 | -------------------------------------------------------------------------------- /setools/commonquery.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014-2015, Tresys Technology, LLC 2 | # 3 | # SPDX-License-Identifier: LGPL-2.1-only 4 | # 5 | from collections.abc import Iterable 6 | import typing 7 | 8 | from . import mixins, policyrep, query 9 | 10 | __all__: typing.Final[tuple[str, ...]] = ("CommonQuery",) 11 | 12 | 13 | class CommonQuery(mixins.MatchPermission, mixins.MatchName, query.PolicyQuery): 14 | 15 | """ 16 | Query common permission sets. 17 | 18 | Parameter: 19 | policy The policy to query. 20 | 21 | Keyword Parameters/Class attributes: 22 | name The name of the common to match. 23 | name_regex If true, regular expression matching will 24 | be used for matching the name. 25 | perms The permissions to match. 26 | perms_equal If true, only commons with permission sets 27 | that are equal to the criteria will 28 | match. Otherwise, any intersection 29 | will match. 30 | perms_regex If true, regular expression matching will be used 31 | on the permission names instead of set logic. 32 | """ 33 | 34 | def results(self) -> Iterable[policyrep.Common]: 35 | """Generator which yields all matching commons.""" 36 | self.log.info(f"Generating common results from {self.policy}") 37 | self._match_name_debug(self.log) 38 | self._match_perms_debug(self.log) 39 | 40 | for com in self.policy.commons(): 41 | if not self._match_name(com): 42 | continue 43 | 44 | if not self._match_perms(com): 45 | continue 46 | 47 | yield com 48 | -------------------------------------------------------------------------------- /setools/defaultquery.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014-2015, Tresys Technology, LLC 2 | # 3 | # SPDX-License-Identifier: LGPL-2.1-only 4 | # 5 | from collections.abc import Iterable 6 | import typing 7 | 8 | from . import mixins, policyrep, query 9 | from .descriptors import CriteriaDescriptor, CriteriaSetDescriptor 10 | 11 | __all__: typing.Final[tuple[str, ...]] = ("DefaultQuery",) 12 | 13 | 14 | class DefaultQuery(mixins.MatchObjClass, query.PolicyQuery): 15 | 16 | """ 17 | Query default_* statements. 18 | 19 | Parameter: 20 | policy The policy to query. 21 | 22 | Keyword Parameters/Class attributes: 23 | ruletype The rule type(s) to match. 24 | tclass The object class(es) to match. 25 | tclass_regex If true, use a regular expression for 26 | matching the rule's object class. 27 | default The default to base new contexts (e.g. "source" or "target") 28 | default_range The range to use on new context, default_range only 29 | ("low", "high", "low_high") 30 | """ 31 | 32 | ruletype = CriteriaSetDescriptor[policyrep.DefaultRuletype]( 33 | enum_class=policyrep.DefaultRuletype) 34 | default = CriteriaDescriptor[policyrep.DefaultValue]( 35 | enum_class=policyrep.DefaultValue) 36 | default_range = CriteriaDescriptor[policyrep.DefaultRangeValue]( 37 | enum_class=policyrep.DefaultRangeValue) 38 | 39 | def results(self) -> Iterable[policyrep.AnyDefault]: 40 | """Generator which yields all matching default_* statements.""" 41 | self.log.info(f"Generating default_* results from {self.policy}") 42 | self.log.debug(f"{self.ruletype=}") 43 | self._match_object_class_debug(self.log) 44 | self.log.debug(f"{self.default=}") 45 | self.log.debug(f"{self.default_range=}") 46 | 47 | for d in self.policy.defaults(): 48 | if self.ruletype and d.ruletype not in self.ruletype: 49 | continue 50 | 51 | if not self._match_object_class(d): 52 | continue 53 | 54 | if self.default and d.default != self.default: 55 | continue 56 | 57 | if self.default_range: 58 | try: 59 | if typing.cast(policyrep.DefaultRange, d).default_range != self.default_range: 60 | continue 61 | except AttributeError: 62 | continue 63 | 64 | yield d 65 | -------------------------------------------------------------------------------- /setools/devicetreeconquery.py: -------------------------------------------------------------------------------- 1 | # Derived from portconquery.py 2 | # 3 | # SPDX-License-Identifier: LGPL-2.1-only 4 | # 5 | from collections.abc import Iterable 6 | import typing 7 | 8 | from . import mixins, policyrep, query 9 | 10 | __all__: typing.Final[tuple[str, ...]] = ("DevicetreeconQuery",) 11 | 12 | 13 | class DevicetreeconQuery(mixins.MatchContext, query.PolicyQuery): 14 | 15 | """ 16 | Devicetreecon context query. 17 | 18 | Parameter: 19 | policy The policy to query. 20 | 21 | Keyword Parameters/Class attributes: 22 | path A single devicetree path. 23 | 24 | user The criteria to match the context's user. 25 | user_regex If true, regular expression matching 26 | will be used on the user. 27 | 28 | role The criteria to match the context's role. 29 | role_regex If true, regular expression matching 30 | will be used on the role. 31 | 32 | type_ The criteria to match the context's type. 33 | type_regex If true, regular expression matching 34 | will be used on the type. 35 | 36 | range_ The criteria to match the context's range. 37 | range_subset If true, the criteria will match if it is a subset 38 | of the context's range. 39 | range_overlap If true, the criteria will match if it overlaps 40 | any of the context's range. 41 | range_superset If true, the criteria will match if it is a superset 42 | of the context's range. 43 | range_proper If true, use proper superset/subset operations. 44 | No effect if not using set operations. 45 | """ 46 | 47 | path: str | None = None 48 | 49 | def results(self) -> Iterable[policyrep.Devicetreecon]: 50 | """Generator which yields all matching devicetreecons.""" 51 | self.log.info(f"Generating results from {self.policy}") 52 | self.log.debug(f"{self.path=}") 53 | self._match_context_debug(self.log) 54 | 55 | for devicetreecon in self.policy.devicetreecons(): 56 | 57 | if self.path and self.path != devicetreecon.path: 58 | continue 59 | 60 | if not self._match_context(devicetreecon.context): 61 | continue 62 | 63 | yield devicetreecon 64 | -------------------------------------------------------------------------------- /setools/diff/conditional.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015-2016, Tresys Technology, LLC 2 | # Copyright 2018, Chris PeBenito 3 | # 4 | # SPDX-License-Identifier: LGPL-2.1-only 5 | # 6 | from collections import defaultdict 7 | 8 | from ..policyrep import Conditional 9 | 10 | from .difference import Wrapper 11 | from .typing import Cache 12 | 13 | 14 | _cond_cache: Cache[Conditional, "ConditionalWrapper"] = defaultdict(dict) 15 | 16 | 17 | def conditional_wrapper_factory(cond: Conditional) -> "ConditionalWrapper": 18 | """ 19 | Wrap type attributes from the specified policy. 20 | 21 | This caches results to prevent duplicate wrapper 22 | objects in memory. 23 | """ 24 | try: 25 | return _cond_cache[cond.policy][cond] 26 | except KeyError: 27 | a = ConditionalWrapper(cond) 28 | _cond_cache[cond.policy][cond] = a 29 | return a 30 | 31 | 32 | class ConditionalWrapper(Wrapper[Conditional]): 33 | 34 | """Wrap conditional policy expressions to allow comparisons by truth table.""" 35 | 36 | __slots__ = ("truth_table") 37 | 38 | def __init__(self, cond: Conditional) -> None: 39 | self.origin = cond 40 | self.truth_table = cond.truth_table() 41 | 42 | def __hash__(self): 43 | return hash(self.origin) 44 | 45 | def __eq__(self, other): 46 | return self.truth_table == other.truth_table 47 | 48 | def __lt__(self, other): 49 | return str(self.origin) < str(other) 50 | -------------------------------------------------------------------------------- /setools/diff/context.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016, Tresys Technology, LLC 2 | # Copyright 2018, Chris PeBenito 3 | # 4 | # SPDX-License-Identifier: LGPL-2.1-only 5 | # 6 | 7 | from ..exception import MLSDisabled 8 | from ..policyrep import Context 9 | 10 | from .difference import Wrapper 11 | from .mls import RangeWrapper 12 | from .roles import role_wrapper_factory 13 | from .types import type_wrapper_factory 14 | from .users import user_wrapper_factory 15 | 16 | 17 | class ContextWrapper(Wrapper[Context]): 18 | 19 | """Wrap contexts to allow comparisons.""" 20 | 21 | __slots__ = ("user", "role", "type_", "range_") 22 | 23 | def __init__(self, ctx: Context) -> None: 24 | self.origin = ctx 25 | self.user = user_wrapper_factory(ctx.user) 26 | self.role = role_wrapper_factory(ctx.role) 27 | self.type_ = type_wrapper_factory(ctx.type_) 28 | self.range_: RangeWrapper | None 29 | 30 | try: 31 | self.range_ = RangeWrapper(ctx.range_) 32 | except MLSDisabled: 33 | self.range_ = None 34 | 35 | def __hash__(self): 36 | return hash(self.origin) 37 | 38 | def __eq__(self, other): 39 | return self.user == other.user and \ 40 | self.role == other.role and \ 41 | self.type_ == other.type_ and \ 42 | self.range_ == other.range_ 43 | 44 | def __lt__(self, other): 45 | return self.user < other.user and \ 46 | self.role < other.role and \ 47 | self.type_ < other.type_ and \ 48 | self.range_ < other.range_ 49 | -------------------------------------------------------------------------------- /setools/diff/descriptors.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015, Tresys Technology, LLC 2 | # 3 | # SPDX-License-Identifier: LGPL-2.1-only 4 | # 5 | 6 | from collections.abc import Callable 7 | import typing 8 | 9 | T = typing.TypeVar("T") 10 | 11 | 12 | class DiffResultDescriptor(typing.Generic[T]): 13 | 14 | """Descriptor for managing diff results.""" 15 | 16 | # @properties could be used instead, but there are so 17 | # many result attributes, this will keep the code cleaner. 18 | 19 | def __init__(self, diff_function: Callable[[typing.Any], None]) -> None: 20 | self.diff_function = diff_function 21 | self.name: str 22 | 23 | def __set_name__(self, owner, name: str) -> None: 24 | self.name = f"_internal_{name}" 25 | 26 | def __get__(self, obj, objtype=None) -> list[T]: 27 | if obj is None: 28 | raise AttributeError 29 | 30 | if getattr(obj, self.name, None) is None: 31 | # must specify obj here since the function is not bound to a 32 | # a particular instance. Otherwise you get: 33 | # TypeError: av_diff_template..diff() missing 1 required 34 | # positional argument: 'self' 35 | self.diff_function(obj) 36 | 37 | return getattr(obj, self.name) 38 | 39 | def __set__(self, obj, value: list[T]) -> None: 40 | setattr(obj, self.name, value) 41 | 42 | def __delete__(self, obj) -> None: 43 | setattr(obj, self.name, None) 44 | -------------------------------------------------------------------------------- /setools/diff/initsid.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016, Tresys Technology, LLC 2 | # 3 | # SPDX-License-Identifier: LGPL-2.1-only 4 | # 5 | from dataclasses import dataclass 6 | 7 | from .. import policyrep 8 | 9 | from .context import ContextWrapper 10 | from .descriptors import DiffResultDescriptor 11 | from .difference import Difference, DifferenceResult, SymbolWrapper 12 | 13 | 14 | @dataclass(frozen=True, order=True) 15 | class ModifiedInitialSID(DifferenceResult): 16 | 17 | """Difference details for a modified initial SID.""" 18 | 19 | isid: policyrep.InitialSID 20 | added_context: policyrep.Context 21 | removed_context: policyrep.Context 22 | 23 | 24 | class InitialSIDsDifference(Difference): 25 | 26 | """Determine the difference in initsids between two policies.""" 27 | 28 | def diff_initialsids(self) -> None: 29 | """Generate the difference in initial SIDs between the policies.""" 30 | 31 | self.log.info( 32 | f"Generating initial SID differences from {self.left_policy} to {self.right_policy}") 33 | 34 | self.added_initialsids, self.removed_initialsids, matched_initialsids = self._set_diff( 35 | (SymbolWrapper(i) for i in self.left_policy.initialsids()), 36 | (SymbolWrapper(i) for i in self.right_policy.initialsids())) 37 | 38 | self.modified_initialsids = list[ModifiedInitialSID]() 39 | 40 | for left_initialsid, right_initialsid in matched_initialsids: 41 | # Criteria for modified initialsids 42 | # 1. change to context 43 | if ContextWrapper(left_initialsid.context) != ContextWrapper(right_initialsid.context): 44 | self.modified_initialsids.append(ModifiedInitialSID( 45 | left_initialsid, right_initialsid.context, left_initialsid.context)) 46 | 47 | added_initialsids = DiffResultDescriptor[policyrep.InitialSID](diff_initialsids) 48 | removed_initialsids = DiffResultDescriptor[policyrep.InitialSID](diff_initialsids) 49 | modified_initialsids = DiffResultDescriptor[ModifiedInitialSID](diff_initialsids) 50 | 51 | # 52 | # Internal functions 53 | # 54 | def _reset_diff(self) -> None: 55 | """Reset diff results on policy changes.""" 56 | self.log.debug("Resetting initialsid differences") 57 | del self.added_initialsids 58 | del self.removed_initialsids 59 | del self.modified_initialsids 60 | -------------------------------------------------------------------------------- /setools/diff/polcap.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016, Tresys Technology, LLC 2 | # 3 | # SPDX-License-Identifier: LGPL-2.1-only 4 | # 5 | from .descriptors import DiffResultDescriptor 6 | from .difference import Difference, SymbolWrapper 7 | 8 | from .. import policyrep 9 | 10 | 11 | class PolCapsDifference(Difference): 12 | 13 | """Determine the difference in polcaps between two policies.""" 14 | 15 | def diff_polcaps(self) -> None: 16 | """Generate the difference in polcaps between the policies.""" 17 | 18 | self.log.info( 19 | f"Generating policy cap differences from {self.left_policy} to {self.right_policy}") 20 | 21 | self.added_polcaps, self.removed_polcaps, _ = self._set_diff( 22 | (SymbolWrapper(n) for n in self.left_policy.polcaps()), 23 | (SymbolWrapper(n) for n in self.right_policy.polcaps())) 24 | 25 | added_polcaps = DiffResultDescriptor[policyrep.PolicyCapability](diff_polcaps) 26 | removed_polcaps = DiffResultDescriptor[policyrep.PolicyCapability](diff_polcaps) 27 | 28 | # 29 | # Internal functions 30 | # 31 | def _reset_diff(self) -> None: 32 | """Reset diff results on policy changes.""" 33 | self.log.debug("Resetting policy capability differences") 34 | del self.added_polcaps 35 | del self.removed_polcaps 36 | -------------------------------------------------------------------------------- /setools/diff/properties.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016, Tresys Technology, LLC 2 | # 3 | # SPDX-License-Identifier: LGPL-2.1-only 4 | # 5 | from dataclasses import dataclass 6 | 7 | from ..policyrep import PolicyEnum 8 | 9 | from .descriptors import DiffResultDescriptor 10 | from .difference import Difference, DifferenceResult 11 | 12 | 13 | @dataclass(frozen=True) 14 | class ModifiedProperty(DifferenceResult): 15 | 16 | """Difference details for a modified policy property.""" 17 | 18 | property: str 19 | added: PolicyEnum | bool | int 20 | removed: PolicyEnum | bool | int 21 | 22 | def __lt__(self, other) -> bool: 23 | return self.property < other.property 24 | 25 | 26 | class PropertiesDifference(Difference): 27 | 28 | """ 29 | Determine the difference in policy properties 30 | (unknown permissions, MLS, etc.) between two policies. 31 | """ 32 | 33 | def diff_properties(self) -> None: 34 | self.modified_properties = list[ModifiedProperty]() 35 | 36 | if self.left_policy.handle_unknown != self.right_policy.handle_unknown: 37 | self.modified_properties.append( 38 | ModifiedProperty("handle_unknown", 39 | self.right_policy.handle_unknown, 40 | self.left_policy.handle_unknown)) 41 | 42 | if self.left_policy.mls != self.right_policy.mls: 43 | self.modified_properties.append( 44 | ModifiedProperty("MLS", 45 | self.right_policy.mls, 46 | self.left_policy.mls)) 47 | 48 | if self.left_policy.version != self.right_policy.version: 49 | self.modified_properties.append( 50 | ModifiedProperty("version", 51 | self.right_policy.version, 52 | self.left_policy.version)) 53 | 54 | modified_properties = DiffResultDescriptor[ModifiedProperty](diff_properties) 55 | 56 | # 57 | # Internal functions 58 | # 59 | def _reset_diff(self) -> None: 60 | """Reset diff results on policy changes.""" 61 | self.log.debug("Resetting property differences") 62 | del self.modified_properties 63 | -------------------------------------------------------------------------------- /setools/diff/typing.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: LGPL-2.1-only 2 | # 3 | from collections import defaultdict 4 | import typing 5 | 6 | from . import difference 7 | from .. import policyrep 8 | 9 | 10 | PE = typing.TypeVar("PE", bound=policyrep.PolicyEnum) 11 | PO = typing.TypeVar("PO", bound=policyrep.PolicyObject) 12 | PS = typing.TypeVar("PS", bound=policyrep.PolicySymbol) 13 | PR = typing.TypeVar("PR", bound=policyrep.AnyConstraint | policyrep.PolicyRule) 14 | WR = typing.TypeVar("WR", bound=difference.Wrapper) 15 | 16 | Cache = defaultdict[policyrep.SELinuxPolicy, dict[PO, WR]] 17 | SymbolCache = Cache[PS, difference.SymbolWrapper[PS]] 18 | 19 | RuleList = defaultdict[PE, list[PR]] | None 20 | -------------------------------------------------------------------------------- /setools/initsidquery.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014-2015, Tresys Technology, LLC 2 | # 3 | # SPDX-License-Identifier: LGPL-2.1-only 4 | # 5 | from collections.abc import Iterable 6 | import typing 7 | 8 | from . import mixins, policyrep, query 9 | 10 | __all__: typing.Final[tuple[str, ...]] = ("InitialSIDQuery",) 11 | 12 | 13 | class InitialSIDQuery(mixins.MatchName, mixins.MatchContext, query.PolicyQuery): 14 | 15 | """ 16 | Initial SID (Initial context) query. 17 | 18 | Parameter: 19 | policy The policy to query. 20 | 21 | Keyword Parameters/Class attributes: 22 | name The Initial SID name to match. 23 | name_regex If true, regular expression matching 24 | will be used on the Initial SID name. 25 | user The criteria to match the context's user. 26 | user_regex If true, regular expression matching 27 | will be used on the user. 28 | role The criteria to match the context's role. 29 | role_regex If true, regular expression matching 30 | will be used on the role. 31 | type_ The criteria to match the context's type. 32 | type_regex If true, regular expression matching 33 | will be used on the type. 34 | range_ The criteria to match the context's range. 35 | range_subset If true, the criteria will match if it is a subset 36 | of the context's range. 37 | range_overlap If true, the criteria will match if it overlaps 38 | any of the context's range. 39 | range_superset If true, the criteria will match if it is a superset 40 | of the context's range. 41 | range_proper If true, use proper superset/subset operations. 42 | No effect if not using set operations. 43 | """ 44 | 45 | def results(self) -> Iterable[policyrep.InitialSID]: 46 | """Generator which yields all matching initial SIDs.""" 47 | self.log.info(f"Generating initial SID results from {self.policy}") 48 | self._match_name_debug(self.log) 49 | self._match_context_debug(self.log) 50 | 51 | for i in self.policy.initialsids(): 52 | if not self._match_name(i): 53 | continue 54 | 55 | if not self._match_context(i.context): 56 | continue 57 | 58 | yield i 59 | -------------------------------------------------------------------------------- /setools/netifconquery.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014-2015, Tresys Technology, LLC 2 | # 3 | # SPDX-License-Identifier: LGPL-2.1-only 4 | # 5 | from collections.abc import Iterable 6 | import typing 7 | 8 | from . import mixins, policyrep, query 9 | 10 | __all__: typing.Final[tuple[str, ...]] = () 11 | 12 | 13 | class NetifconQuery(mixins.MatchContext, mixins.MatchName, query.PolicyQuery): 14 | 15 | """ 16 | Network interface context query. 17 | 18 | Parameter: 19 | policy The policy to query. 20 | 21 | Keyword Parameters/Class attributes: 22 | name The name of the network interface to match. 23 | name_regex If true, regular expression matching will 24 | be used for matching the name. 25 | user The criteria to match the context's user. 26 | user_regex If true, regular expression matching 27 | will be used on the user. 28 | role The criteria to match the context's role. 29 | role_regex If true, regular expression matching 30 | will be used on the role. 31 | type_ The criteria to match the context's type. 32 | type_regex If true, regular expression matching 33 | will be used on the type. 34 | range_ The criteria to match the context's range. 35 | range_subset If true, the criteria will match if it is a subset 36 | of the context's range. 37 | range_overlap If true, the criteria will match if it overlaps 38 | any of the context's range. 39 | range_superset If true, the criteria will match if it is a superset 40 | of the context's range. 41 | range_proper If true, use proper superset/subset operations. 42 | No effect if not using set operations. 43 | """ 44 | 45 | def results(self) -> Iterable[policyrep.Netifcon]: 46 | """Generator which yields all matching netifcons.""" 47 | self.log.info(f"Generating netifcon results from {self.policy}") 48 | self._match_name_debug(self.log) 49 | self._match_context_debug(self.log) 50 | 51 | for netif in self.policy.netifcons(): 52 | if not self._match_name(netif.netif): 53 | continue 54 | 55 | if not self._match_context(netif.context): 56 | continue 57 | 58 | yield netif 59 | -------------------------------------------------------------------------------- /setools/polcapquery.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014-2015, Tresys Technology, LLC 2 | # 3 | # SPDX-License-Identifier: LGPL-2.1-only 4 | # 5 | from collections.abc import Iterable 6 | import typing 7 | 8 | from . import mixins, policyrep, query 9 | 10 | __all__: typing.Final[tuple[str, ...]] = ("PolCapQuery",) 11 | 12 | 13 | class PolCapQuery(mixins.MatchName, query.PolicyQuery): 14 | 15 | """ 16 | Query SELinux policy capabilities 17 | 18 | Parameter: 19 | policy The policy to query. 20 | 21 | Keyword Parameters/Class attributes: 22 | name The name of the policy capability to match. 23 | name_regex If true, regular expression matching will 24 | be used for matching the name. 25 | """ 26 | 27 | def results(self) -> Iterable[policyrep.PolicyCapability]: 28 | """Generator which yields all matching policy capabilities.""" 29 | self.log.info(f"Generating policy capability results from {self.policy}") 30 | self._match_name_debug(self.log) 31 | 32 | for cap in self.policy.polcaps(): 33 | if not self._match_name(cap): 34 | continue 35 | 36 | yield cap 37 | -------------------------------------------------------------------------------- /setools/policyrep/context.pxi: -------------------------------------------------------------------------------- 1 | # Copyright 2014-2015, Tresys Technology, LLC 2 | # Copyright 2016-2018, Chris PeBenito 3 | # 4 | # SPDX-License-Identifier: LGPL-2.1-only 5 | # 6 | 7 | cdef class Context(PolicyObject): 8 | 9 | """A SELinux security context/security attribute.""" 10 | 11 | cdef: 12 | readonly User user 13 | readonly Role role 14 | readonly Type type_ 15 | Range _range 16 | 17 | @staticmethod 18 | cdef inline Context factory(SELinuxPolicy policy, sepol.context_struct_t *symbol): 19 | """Factory function for creating Context objects.""" 20 | cdef Context c = Context.__new__(Context) 21 | c.policy = policy 22 | c.key = symbol 23 | c.user = User.factory(policy, policy.user_value_to_datum(symbol.user - 1)) 24 | c.role = Role.factory(policy, policy.role_value_to_datum(symbol.role - 1)) 25 | c.type_ = Type.factory(policy, policy.type_value_to_datum(symbol.type - 1)) 26 | 27 | if policy.mls: 28 | c._range = Range.factory(policy, &symbol.range) 29 | 30 | return c 31 | 32 | def __str__(self): 33 | if self._range: 34 | return f"{self.user}:{self.role}:{self.type_}:{self.range_}" 35 | else: 36 | return f"{self.user}:{self.role}:{self.type_}" 37 | 38 | @property 39 | def range_(self): 40 | """The MLS range of the context.""" 41 | if self._range: 42 | return self._range 43 | else: 44 | raise MLSDisabled 45 | 46 | def statement(self): 47 | raise NoStatement 48 | -------------------------------------------------------------------------------- /setools/policyrep/initsid.pxi: -------------------------------------------------------------------------------- 1 | # Copyright 2014, Tresys Technology, LLC 2 | # Copyright 2017-2018, Chris PeBenito 3 | # 4 | # SPDX-License-Identifier: LGPL-2.1-only 5 | # 6 | 7 | # 8 | # Constants 9 | # 10 | # Binary policy does not contain the SID names 11 | SELINUX_SIDNAMES = ("undefined", "kernel", "security", "unlabeled", "fs", "file", "file_labels", 12 | "init", "any_socket", "port", "netif", "netmsg", "node", "igmp_packet", "icmp_socket", 13 | "tcp_socket", "sysctl_modprobe", "sysctl", "sysctl_fs", "sysctl_kernel", "sysctl_net", 14 | "sysctl_net_unix", "sysctl_vm", "sysctl_dev", "kmod", "policy", "scmp_packet", "devnull") 15 | 16 | 17 | XEN_SIDNAMES = ("xen", "dom0", "domxen", "domio", "unlabeled", "security", "irq", "iomem", "ioport", 18 | "device", "domU", "domDM") 19 | 20 | 21 | # 22 | # Classes 23 | # 24 | cdef class InitialSID(Ocontext): 25 | 26 | """An initial SID statement.""" 27 | 28 | cdef readonly str name 29 | 30 | @staticmethod 31 | cdef inline InitialSID factory(SELinuxPolicy policy, sepol.ocontext *symbol): 32 | """Factory function for creating InitialSID objects.""" 33 | cdef InitialSID i = InitialSID.__new__(InitialSID) 34 | i.policy = policy 35 | i.key = symbol 36 | i.context = Context.factory(policy, symbol.context) 37 | 38 | if symbol.u.name: 39 | i.name = intern(symbol.u.name) 40 | elif policy.target_platform == PolicyTarget.selinux: 41 | i.name = SELINUX_SIDNAMES[symbol.sid[0]] 42 | elif policy.target_platform == PolicyTarget.xen: 43 | i.name = XEN_SIDNAMES[symbol.sid[0]] 44 | else: 45 | raise NotImplementedError 46 | 47 | return i 48 | 49 | def __str__(self): 50 | return self.name 51 | 52 | def statement(self): 53 | return f"sid {self.name} {self.context}" 54 | 55 | 56 | cdef class InitialSIDIterator(OcontextIterator): 57 | 58 | """Iterator for initial SID statements in the policy.""" 59 | 60 | @staticmethod 61 | cdef factory(SELinuxPolicy policy, sepol.ocontext_t *head): 62 | """Factory function for creating initial SID iterators.""" 63 | i = InitialSIDIterator() 64 | i.policy = policy 65 | i.head = i.curr = head 66 | return i 67 | 68 | def __next__(self): 69 | super().__next__() 70 | return InitialSID.factory(self.policy, self.ocon) 71 | -------------------------------------------------------------------------------- /setools/policyrep/polcap.pxi: -------------------------------------------------------------------------------- 1 | # Copyright 2014-2015, Tresys Technology, LLC 2 | # Copyright 2017-2018, Chris PeBenito 3 | # 4 | # SPDX-License-Identifier: LGPL-2.1-only 5 | # 6 | 7 | 8 | cdef class PolicyCapability(PolicySymbol): 9 | 10 | """A policy capability.""" 11 | 12 | @staticmethod 13 | cdef inline PolicyCapability factory(SELinuxPolicy policy, size_t bit): 14 | """Factory function for creating PolicyCapability objects.""" 15 | cdef PolicyCapability r = PolicyCapability.__new__(PolicyCapability) 16 | r.policy = policy 17 | r.name = intern(sepol.sepol_polcap_getname(bit)) 18 | return r 19 | 20 | def __eq__(self, other): 21 | try: 22 | return self.policy == other.policy \ 23 | and self.name == other.name 24 | except AttributeError: 25 | return self.name == str(other) 26 | 27 | def __hash__(self): 28 | return hash(self.name) 29 | 30 | def statement(self): 31 | return f"policycap {self.name};" 32 | 33 | 34 | cdef class PolicyCapabilityIterator(EbitmapIterator): 35 | 36 | """Iterator for policy capability statements in the policy.""" 37 | 38 | @staticmethod 39 | cdef factory(SELinuxPolicy policy, sepol.ebitmap_t *bmap): 40 | """Factory function for creating PolicyCapability iterators.""" 41 | i = PolicyCapabilityIterator() 42 | i.policy = policy 43 | i.bmap = bmap 44 | i.reset() 45 | return i 46 | 47 | def __next__(self): 48 | super().__next__() 49 | return PolicyCapability.factory(self.policy, self.bit) 50 | -------------------------------------------------------------------------------- /setools/policyrep/rule.pxi: -------------------------------------------------------------------------------- 1 | # Copyright 2014, 2016, Tresys Technology, LLC 2 | # Copyright 2017-2018, Chris PeBenito 3 | # 4 | # SPDX-License-Identifier: LGPL-2.1-only 5 | # 6 | 7 | cdef class PolicyRule(PolicyObject): 8 | 9 | """This is base class for policy rules.""" 10 | 11 | cdef: 12 | readonly object ruletype 13 | readonly object source 14 | readonly object target 15 | readonly object origin 16 | # This is initialized to False: 17 | readonly bint extended 18 | 19 | @property 20 | def conditional(self): 21 | """The conditional expression for this rule.""" 22 | # Most rule types cannot be conditional. 23 | raise RuleNotConditional 24 | 25 | @property 26 | def conditional_block(self): 27 | """The conditional block of the rule (T/F)""" 28 | # Most rule types cannot be conditional. 29 | raise RuleNotConditional 30 | 31 | @property 32 | def default(self): 33 | """The rule's default.""" 34 | raise RuleUseError 35 | 36 | @property 37 | def filename(self): 38 | """The filename for this rule.""" 39 | # Most rule types do not have filenames. 40 | raise RuleUseError 41 | 42 | @property 43 | def perms(self): 44 | """The permissions for this rule.""" 45 | raise RuleUseError 46 | 47 | @property 48 | def tclass(self): 49 | """The rule's object class.""" 50 | raise RuleUseError 51 | 52 | @property 53 | def xperm_type(self): 54 | """The extended permission type for this rule.""" 55 | # Most rule types are not extended. 56 | raise RuleUseError 57 | 58 | def enabled(self, **kwargs): 59 | """ 60 | Determine if the rule is enabled, given the stated boolean values. 61 | 62 | Keyword Parameters: bool_name=True|False 63 | Each keyword parameter name corresponds to a Boolean name 64 | in the expression and the state to use in the evaluation. 65 | If a Boolean value is not set, its default value is used. 66 | Extra values are ignored. 67 | 68 | Return: bool 69 | """ 70 | # Most rule types cannot be conditional, thus are always enabled. 71 | return True 72 | 73 | def expand(self): 74 | """Expand the rule into an equivalent set of rules without attributes.""" 75 | raise NotImplementedError 76 | -------------------------------------------------------------------------------- /setools/policyrep/selinux.pxd: -------------------------------------------------------------------------------- 1 | # Copyright 2018, Chris PeBenito 2 | # 3 | # SPDX-License-Identifier: LGPL-2.1-only 4 | # 5 | 6 | # Directly use libselinux rather than the Python bindings, since 7 | # only a few functions are needed. 8 | 9 | cdef extern from "": 10 | bint selinuxfs_exists() 11 | const char* selinux_current_policy_path() 12 | const char* selinux_binary_policy_path() 13 | char* selinux_boolean_sub(const char *boolean_name); 14 | -------------------------------------------------------------------------------- /setools/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SELinuxProject/setools/7503c647eecaead2fec30c938eadba0f4a01a69b/setools/py.typed -------------------------------------------------------------------------------- /setools/query.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014-2015, Tresys Technology, LLC 2 | # Copyright 2018, Chris PeBenito 3 | # 4 | # SPDX-License-Identifier: LGPL-2.1-only 5 | # 6 | from abc import ABC, abstractmethod 7 | import logging 8 | import typing 9 | 10 | if typing.TYPE_CHECKING: 11 | from networkx import DiGraph 12 | from .policyrep import SELinuxPolicy 13 | 14 | 15 | class PolicyQuery(ABC): 16 | 17 | """Abstract base class for all SELinux policy analyses.""" 18 | 19 | def __init__(self, policy: "SELinuxPolicy", **kwargs) -> None: 20 | self.policy: "SELinuxPolicy" = policy 21 | self.log: typing.Final = logging.getLogger(self.__module__) 22 | 23 | # keys are sorted in reverse order so regex settings 24 | # are set before the criteria, e.g. name_regex 25 | # is set before name. This ensures correct behavior 26 | # since the criteria descriptors are sensitive to 27 | # regex settings. 28 | for name in sorted(kwargs.keys(), reverse=True): 29 | attr = getattr(self, name, None) # None is not callable 30 | if callable(attr): 31 | raise ValueError(f"Keyword parameter {name} conflicts with a callable.") 32 | 33 | setattr(self, name, kwargs[name]) 34 | 35 | @abstractmethod 36 | def results(self) -> typing.Iterable: 37 | """ 38 | Generator which returns the matches for the query. This method 39 | should be overridden by subclasses. 40 | """ 41 | 42 | 43 | class DirectedGraphAnalysis(PolicyQuery): 44 | 45 | """Abstract base class for graph-basded SELinux policy analysis.""" 46 | 47 | G: "DiGraph" 48 | 49 | @abstractmethod 50 | def graphical_results(self) -> "DiGraph": 51 | """Return the results of the analysis as a NetworkX directed graph.""" 52 | -------------------------------------------------------------------------------- /setools/rolequery.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014-2015, Tresys Technology, LLC 2 | # 3 | # SPDX-License-Identifier: LGPL-2.1-only 4 | # 5 | from collections.abc import Iterable 6 | import typing 7 | 8 | from . import mixins, policyrep, query, util 9 | from .descriptors import CriteriaSetDescriptor 10 | 11 | __all__: typing.Final[tuple[str, ...]] = ("RoleQuery",) 12 | 13 | 14 | class RoleQuery(mixins.MatchName, query.PolicyQuery): 15 | 16 | """ 17 | Query SELinux policy roles. 18 | 19 | Parameter: 20 | policy The policy to query. 21 | 22 | Keyword Parameters/Class attributes: 23 | name The role name to match. 24 | name_regex If true, regular expression matching 25 | will be used on the role names. 26 | types The type to match. 27 | types_equal If true, only roles with type sets 28 | that are equal to the criteria will 29 | match. Otherwise, any intersection 30 | will match. 31 | types_regex If true, regular expression matching 32 | will be used on the type names instead 33 | of set logic. 34 | """ 35 | 36 | types = CriteriaSetDescriptor[policyrep.Type]("types_regex", "lookup_type") 37 | types_equal: bool = False 38 | types_regex: bool = False 39 | 40 | def results(self) -> Iterable[policyrep.Role]: 41 | """Generator which yields all matching roles.""" 42 | self.log.info(f"Generating role results from {self.policy}") 43 | self._match_name_debug(self.log) 44 | self.log.debug(f"{self.types=}, {self.types_regex=}, {self.types_equal=}") 45 | 46 | for r in self.policy.roles(): 47 | if not self._match_name(r): 48 | continue 49 | 50 | if self.types and not util.match_regex_or_set( 51 | set(r.types()), 52 | self.types, 53 | self.types_equal, 54 | self.types_regex): 55 | continue 56 | 57 | yield r 58 | -------------------------------------------------------------------------------- /setools/roletypesquery.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025, Christian Göttsche 2 | # 3 | # SPDX-License-Identifier: LGPL-2.1-only 4 | # 5 | from collections.abc import Iterable 6 | import typing 7 | 8 | from . import mixins, policyrep, query 9 | 10 | __all__: typing.Final[tuple[str, ...]] = ("RoleTypesQuery",) 11 | 12 | 13 | class RoleTypesQuery(mixins.MatchName, query.PolicyQuery): 14 | 15 | """ 16 | Query SELinux policy roles. 17 | 18 | Parameter: 19 | policy The policy to query. 20 | 21 | Keyword Parameters/Class attributes: 22 | name The type name to match. 23 | name_regex If true, regular expression matching 24 | will be used on the type names. 25 | """ 26 | 27 | def results(self) -> Iterable[policyrep.Role]: 28 | """Generator which yields all matching roles.""" 29 | self.log.info(f"Generating role-types results from {self.policy}") 30 | self._match_name_debug(self.log) 31 | 32 | for r in self.policy.roles(): 33 | for t in r.types(): 34 | if self._match_name(t): 35 | yield r 36 | break 37 | -------------------------------------------------------------------------------- /setools/sensitivityquery.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015, Tresys Technology, LLC 2 | # 3 | # SPDX-License-Identifier: LGPL-2.1-only 4 | # 5 | from collections.abc import Iterable 6 | 7 | from . import policyrep 8 | from .descriptors import CriteriaDescriptor 9 | from .mixins import MatchAlias, MatchName 10 | from .query import PolicyQuery 11 | 12 | 13 | class SensitivityQuery(MatchAlias, MatchName, PolicyQuery): 14 | 15 | """ 16 | Query MLS Sensitivities 17 | 18 | Parameter: 19 | policy The policy to query. 20 | 21 | Keyword Parameters/Class attributes: 22 | name The name of the category to match. 23 | name_regex If true, regular expression matching will 24 | be used for matching the name. 25 | alias The alias name to match. 26 | alias_regex If true, regular expression matching 27 | will be used on the alias names. 28 | sens The criteria to match the sensitivity by dominance. 29 | sens_dom If true, the criteria will match if it dominates 30 | the sensitivity. 31 | sens_domby If true, the criteria will match if it is dominated 32 | by the sensitivity. 33 | """ 34 | 35 | sens = CriteriaDescriptor[policyrep.Sensitivity](lookup_function="lookup_sensitivity") 36 | sens_dom: bool = False 37 | sens_domby: bool = False 38 | 39 | def results(self) -> Iterable[policyrep.Sensitivity]: 40 | """Generator which yields all matching sensitivities.""" 41 | self.log.info(f"Generating sensitivity results from {self.policy}") 42 | self._match_name_debug(self.log) 43 | self._match_alias_debug(self.log) 44 | self.log.debug(f"{self.sens=}, {self.sens_dom=}, {self.sens_domby=}") 45 | 46 | for s in self.policy.sensitivities(): 47 | if not self._match_name(s): 48 | continue 49 | 50 | if not self._match_alias(s): 51 | continue 52 | 53 | if self.sens: 54 | if self.sens_dom: 55 | if self.sens < s: 56 | continue 57 | elif self.sens_domby: 58 | if self.sens > s: 59 | continue 60 | elif self.sens != s: 61 | continue 62 | 63 | yield s 64 | -------------------------------------------------------------------------------- /setools/typeattrquery.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014-2015, Tresys Technology, LLC 2 | # 3 | # SPDX-License-Identifier: LGPL-2.1-only 4 | # 5 | from collections.abc import Iterable 6 | 7 | from . import policyrep, util 8 | from .descriptors import CriteriaSetDescriptor 9 | from .mixins import MatchName 10 | from .query import PolicyQuery 11 | 12 | 13 | class TypeAttributeQuery(MatchName, PolicyQuery): 14 | 15 | """ 16 | Query SELinux policy type attributes. 17 | 18 | Parameter: 19 | policy The policy to query. 20 | 21 | Keyword Parameters/Class attributes: 22 | name The type name to match. 23 | name_regex If true, regular expression matching 24 | will be used on the type names. 25 | types The type to match. 26 | types_equal If true, only attributes with type sets 27 | that are equal to the criteria will 28 | match. Otherwise, any intersection 29 | will match. 30 | types_regex If true, regular expression matching 31 | will be used on the type names instead 32 | of set logic. 33 | """ 34 | 35 | types = CriteriaSetDescriptor[policyrep.Type]("types_regex", "lookup_type") 36 | types_equal: bool = False 37 | types_regex: bool = False 38 | 39 | def results(self) -> Iterable[policyrep.TypeAttribute]: 40 | """Generator which yields all matching types.""" 41 | self.log.info(f"Generating type attribute results from {self.policy}") 42 | self._match_name_debug(self.log) 43 | self.log.debug(f"{self.types=}, {self.types_regex=}, {self.types_equal=}") 44 | 45 | for attr in self.policy.typeattributes(): 46 | if not self._match_name(attr): 47 | continue 48 | 49 | if self.types and not util.match_regex_or_set( 50 | set(attr.expand()), 51 | self.types, 52 | self.types_equal, 53 | self.types_regex): 54 | continue 55 | 56 | yield attr 57 | -------------------------------------------------------------------------------- /setoolsgui/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015, Tresys Technology, LLC 2 | # 3 | # SPDX-License-Identifier: LGPL-2.1-only 4 | # 5 | # 6 | 7 | from .apol import run_apol 8 | 9 | import logging 10 | logging.getLogger(__name__).addHandler(logging.NullHandler()) 11 | -------------------------------------------------------------------------------- /setoolsgui/apol.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Generic styles 3 | */ 4 | 5 | QGroupBox { 6 | border: 1px solid lightgrey; 7 | margin-top: 0.5em; 8 | } 9 | 10 | QGroupBox::title { 11 | subcontrol-origin: margin; 12 | left: 10px; 13 | padding: 0 3px 0 3px; 14 | } 15 | 16 | /* 17 | * Specific styles 18 | */ 19 | ChooseAnalysis { 20 | width: 400px; 21 | height: 420px; 22 | } 23 | 24 | ExcludeTypes { 25 | width: 620px; 26 | height: 340px; 27 | } 28 | 29 | ExcludeTypes#header { 30 | font-style: bold; 31 | } 32 | 33 | HtmlHelpDialog { 34 | min-width: 640px; 35 | min-height: 480px; 36 | } 37 | 38 | PermissionMapEditor { 39 | width: 750px; 40 | height: 450px; 41 | } 42 | 43 | QLabel#title { 44 | font-size: 14pt; 45 | font-style: bold; 46 | } 47 | 48 | QLabel#error_message { 49 | color: red; 50 | font-weight: bold; 51 | } 52 | 53 | QMainWindow { 54 | width: 1024px; 55 | height: 768px; 56 | min-width: 800px; 57 | min-height: 600px; 58 | } 59 | 60 | QPlainTextEdit#raw_results { 61 | font-family: monospace 62 | } 63 | -------------------------------------------------------------------------------- /setoolsgui/apol.html: -------------------------------------------------------------------------------- 1 | 2 | Apol 3 |

Apol SELinux Policy Analysis

4 | 5 |

Overview

6 | 7 |

This file contains basic help information for using apol, a graphical 8 | policy analysis tool for Security Enhanced (SELinux) policies. The 9 | tool provides the ability to:

10 | 11 |
    12 |
  1. 13 | Examine, search, and relate policy components (types, type 14 | attributes, object classes, object permissions, roles, users, 15 | initials SIDs, MLS components, network and file system contexts, 16 | and booleans), and policy rules.
  2. 17 | 18 |
  3. Perform some automated analysis of policies, including forward and 19 | reverse domain transition analyses, and information flow analysis.
  4. 20 |
21 | 22 |

Apol supports source, and binary policies. Certain apol features may 23 | be disabled if the underlying policy does not support the action. For 24 | example, rule searches will not report line numbers when searching 25 | monolithic binary polices. 26 | 27 |

Apol provides compatibility with the current and previous policy 28 | syntax. It supports analysis of policy versions 15 and up.

29 | 30 | 31 |

Menus

32 |

Use Open from the File menu to open a valid policy. 33 | Only one policy can be open at a time; opening a second policy will 34 | result in the first being closed.

35 | 36 |

The Permission Map menu allows for opening, editing, 37 | and saving permission mappings. 38 | These are used by apol's information flow analysis.

39 | 40 |

Starting an Analysis

41 |

To begin analyzing a policy, click the new analysis button. 42 | A menu of available analysis tools will be presented. Select one, and 43 | a new analysis tab will open. Any analysis can be started multiple 44 | times, and each will operate independently, so multiple concurrent 45 | analyses can be performed. To help manage multiple tabs, the tabs can be 46 | renamed by double-click the tab.

47 | -------------------------------------------------------------------------------- /setoolsgui/config.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019, Chris PeBenito 2 | # 3 | # SPDX-License-Identifier: LGPL-2.1-only 4 | # 5 | # 6 | 7 | import os 8 | import logging 9 | import configparser 10 | import threading 11 | import typing 12 | 13 | # 14 | # Configfile constants 15 | # 16 | APOLCONFIG: typing.Final[str] = "~/.config/setools/apol.conf" 17 | HELP_SECTION: typing.Final[str] = "Help" 18 | HELP_PGM: typing.Final[str] = "assistant" 19 | DEFAULT_HELP_PGM: typing.Final[str] = "/usr/bin/assistant" 20 | 21 | 22 | class ApolConfig: 23 | 24 | """Apol configuration file.""" 25 | 26 | def __init__(self) -> None: 27 | self.log: typing.Final = logging.getLogger(__name__) 28 | self._lock = threading.Lock() 29 | self.path: typing.Final = os.path.expanduser(APOLCONFIG) 30 | 31 | self._config = configparser.ConfigParser() 32 | save = False 33 | 34 | if not self._config.read((self.path,)): 35 | save = True 36 | 37 | if not self._config.has_section(HELP_SECTION): 38 | self._config.add_section(HELP_SECTION) 39 | self._config.set(HELP_SECTION, HELP_PGM, DEFAULT_HELP_PGM) 40 | save = True 41 | 42 | if save: 43 | self.save() 44 | 45 | def save(self) -> None: 46 | """Save configuration file.""" 47 | with self._lock: 48 | try: 49 | os.makedirs(os.path.dirname(self.path), mode=0o755, exist_ok=True) 50 | 51 | with open(self.path, "w") as fd: 52 | self._config.write(fd) 53 | 54 | except Exception: 55 | self.log.critical(f"Failed to save configuration file \"{self.path}\"") 56 | self.log.debug("Backtrace", exc_info=True) 57 | 58 | @property 59 | def assistant(self) -> str: 60 | """Return the help program executable path.""" 61 | with self._lock: 62 | return self._config.get(HELP_SECTION, HELP_PGM, fallback=DEFAULT_HELP_PGM) 63 | 64 | @assistant.setter 65 | def assistant(self, value: str) -> None: 66 | with self._lock: 67 | self._config.set(HELP_SECTION, HELP_PGM, value) 68 | -------------------------------------------------------------------------------- /setoolsgui/widgets/__init__.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: LGPL-2.1-only 2 | -------------------------------------------------------------------------------- /setoolsgui/widgets/criteria/__init__.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: LGPL-2.1-only 2 | 3 | from .criteria import * 4 | 5 | from .boolean import * 6 | from .boundsruletype import * 7 | from .comboenum import * 8 | from .common import * 9 | from .constraintype import * 10 | from .context import * 11 | from .defaultruletype import * 12 | from .defaultvalue import * 13 | from .fsuseruletype import * 14 | from .infiniband import * 15 | from .ipnetwork import * 16 | from .ipports import * 17 | from .mls import * 18 | from .mlslevelrange import * 19 | from .mlsruletype import * 20 | from .name import * 21 | from .objclass import * 22 | from .permission import * 23 | from .radioenum import * 24 | from .rbacruletype import * 25 | from .role import * 26 | from .teruletype import * 27 | from .type import * 28 | from .typeattr import * 29 | from .user import * 30 | -------------------------------------------------------------------------------- /setoolsgui/widgets/criteria/boundsruletype.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: LGPL-2.1-only 2 | import typing 3 | 4 | from PyQt6 import QtWidgets 5 | import setools 6 | 7 | from .checkboxset import CheckboxSetWidget 8 | 9 | # Checked by default: 10 | DEFAULT_CHECKED: typing.Final[tuple[str, ...]] = ("typebounds",) 11 | 12 | __all__ = ('BoundsRuleType',) 13 | 14 | 15 | class BoundsRuleType(CheckboxSetWidget): 16 | 17 | """ 18 | Criteria selection widget presenting bounds rule types as a series 19 | of checkboxes. The selected checkboxes are then merged into a single Python 20 | list consisting of object names (bounds ruletypes) and stored in the query's 21 | specified attribute. 22 | """ 23 | 24 | def __init__(self, title: str, query: setools.PolicyQuery, attrname: str = "ruletype", 25 | parent: QtWidgets.QWidget | None = None) -> None: 26 | 27 | super().__init__(title, query, attrname, (rt.name for rt in setools.BoundsRuletype), 28 | num_cols=2, parent=parent) 29 | 30 | for name, widget in self.criteria.items(): 31 | widget.setChecked(name in DEFAULT_CHECKED) 32 | widget.setToolTip(f"Match {name} rules.") 33 | widget.setWhatsThis( 34 | f""" 35 |

Match {name} rules

36 | 37 |

If a rule has the {name} rule type, it will be returned.

38 | """) 39 | 40 | 41 | if __name__ == '__main__': 42 | import sys 43 | import warnings 44 | import pprint 45 | import logging 46 | 47 | logging.basicConfig(level=logging.DEBUG, 48 | format='%(asctime)s|%(levelname)s|%(name)s|%(message)s') 49 | warnings.simplefilter("default") 50 | 51 | q = setools.BoundsQuery(setools.SELinuxPolicy()) 52 | 53 | app = QtWidgets.QApplication(sys.argv) 54 | mw = QtWidgets.QMainWindow() 55 | w = BoundsRuleType("Test default ruletypes", q, parent=mw) 56 | w.setToolTip("test tooltip") 57 | w.setWhatsThis("test what's this") 58 | mw.setCentralWidget(w) 59 | mw.resize(w.size()) 60 | whatsthis = QtWidgets.QWhatsThis.createAction(mw) 61 | mw.menuBar().addAction(whatsthis) # type: ignore[union-attr] 62 | mw.show() 63 | rc = app.exec() 64 | print("Ruletypes set in query:") 65 | pprint.pprint(q.ruletype) 66 | sys.exit(rc) 67 | -------------------------------------------------------------------------------- /setoolsgui/widgets/criteria/combobox.py: -------------------------------------------------------------------------------- 1 | 2 | from contextlib import suppress 3 | 4 | from PyQt6 import QtWidgets 5 | import setools 6 | 7 | from .criteria import CriteriaWidget 8 | 9 | __all__ = ("ComboBoxWidget",) 10 | 11 | 12 | class ComboBoxWidget(CriteriaWidget): 13 | 14 | """Criteria selection widget presenting options as a QComboBox.""" 15 | 16 | def __init__(self, title: str, query: setools.PolicyQuery, attrname: str, /, *, 17 | enable_any: bool = True, parent: QtWidgets.QWidget | None = None) -> None: 18 | 19 | super().__init__(title, query, attrname, parent=parent) 20 | self.top_layout = QtWidgets.QHBoxLayout(self) 21 | 22 | self.criteria = QtWidgets.QComboBox(self) 23 | self.criteria.setEditable(False) 24 | self.criteria.setSizePolicy(QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, 25 | QtWidgets.QSizePolicy.Policy.Fixed)) 26 | self.criteria.currentIndexChanged.connect(self._update_query) 27 | self.top_layout.addWidget(self.criteria) 28 | 29 | if enable_any: 30 | self.criteria.addItem("[Any]", None) 31 | 32 | # add spacer so that the combo box is left-aligned 33 | spacerItem = QtWidgets.QSpacerItem(40, 20, 34 | QtWidgets.QSizePolicy.Policy.Expanding, 35 | QtWidgets.QSizePolicy.Policy.Minimum) 36 | self.top_layout.addItem(spacerItem) 37 | 38 | @property 39 | def has_errors(self) -> bool: 40 | """Get error state of this widget.""" 41 | return False 42 | 43 | def _update_query(self, idx: int) -> None: 44 | """Update the query based on the combo box.""" 45 | value = self.criteria.itemText(idx) 46 | if value: 47 | # get enum value from combo box 48 | value = self.criteria.itemData(idx) 49 | 50 | self.log.debug(f"Setting {self.attrname} to {value!r}") 51 | setattr(self.query, self.attrname, value) 52 | 53 | # 54 | # Workspace methods 55 | # 56 | 57 | def save(self, settings: dict) -> None: 58 | settings[self.attrname] = self.criteria.currentText() 59 | 60 | def load(self, settings: dict) -> None: 61 | with suppress(AttributeError, KeyError): 62 | idx = self.criteria.findText(settings[self.attrname]) 63 | self.criteria.setCurrentIndex(idx) 64 | -------------------------------------------------------------------------------- /setoolsgui/widgets/criteria/comboenum.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: LGPL-2.1-only 2 | 3 | import enum 4 | import typing 5 | 6 | from PyQt6 import QtWidgets 7 | import setools 8 | 9 | from .combobox import ComboBoxWidget 10 | 11 | E = typing.TypeVar("E", bound=enum.Enum) 12 | 13 | __all__ = ('ComboEnumWidget',) 14 | 15 | 16 | class ComboEnumWidget(ComboBoxWidget, typing.Generic[E]): 17 | 18 | """Criteria selection widget presenting possible options a QComboxBox.""" 19 | 20 | def __init__(self, title: str, query: setools.PolicyQuery, attrname: str, enum_class: type[E], 21 | enable_any: bool = True, parent: QtWidgets.QWidget | None = None) -> None: 22 | 23 | super().__init__(title, query, attrname, enable_any=enable_any, parent=parent) 24 | 25 | enu: E 26 | for enu in sorted(enum_class, key=lambda e: e.name): 27 | self.criteria.addItem(enu.name, enu) 28 | 29 | 30 | if __name__ == '__main__': 31 | import sys 32 | import logging 33 | import pprint 34 | import warnings 35 | 36 | class local_enum_class(enum.Enum): 37 | """Enum for local testing""" 38 | Val1 = "Value 1" 39 | Val4 = "Value 4" 40 | Val2 = "Value 2" 41 | Val3 = "Value 3" 42 | 43 | logging.basicConfig(level=logging.DEBUG, 44 | format='%(asctime)s|%(levelname)s|%(name)s|%(message)s') 45 | warnings.simplefilter("default") 46 | 47 | q = setools.TERuleQuery(setools.SELinuxPolicy()) 48 | 49 | app = QtWidgets.QApplication(sys.argv) 50 | mw = QtWidgets.QMainWindow() 51 | widget = ComboEnumWidget("Test radio enum", q, "radioattrname", local_enum_class, parent=mw) 52 | widget.setToolTip("test tooltip") 53 | widget.setWhatsThis("test what's this") 54 | mw.setCentralWidget(widget) 55 | mw.resize(widget.size()) 56 | whatsthis = QtWidgets.QWhatsThis.createAction(mw) 57 | mw.menuBar().addAction(whatsthis) # type: ignore[union-attr] 58 | mw.show() 59 | rc = app.exec() 60 | local_settings: typing.Dict[str, str] = {} 61 | widget.save(local_settings) 62 | pprint.pprint(local_settings) 63 | sys.exit(rc) 64 | -------------------------------------------------------------------------------- /setoolsgui/widgets/criteria/common.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: LGPL-2.1-only 2 | 3 | from PyQt6 import QtWidgets 4 | import setools 5 | 6 | from .criteria import OptionsPlacement 7 | from .name import NameWidget 8 | 9 | # Regex for exact matches to roles 10 | VALIDATE_EXACT = r"[A-Za-z0-9._-]*" 11 | 12 | __all__ = ("CommonName",) 13 | 14 | 15 | class CommonName(NameWidget): 16 | 17 | """ 18 | Widget providing a QLineEdit that saves the input to the attributes 19 | of the specified query. This supports inputs of common names. 20 | """ 21 | 22 | def __init__(self, title: str, query: setools.PolicyQuery, attrname: str, /, *, 23 | parent: QtWidgets.QWidget | None = None, 24 | options_placement: OptionsPlacement = OptionsPlacement.RIGHT, 25 | required: bool = False, enable_regex: bool = True): 26 | 27 | # Create completion list 28 | completion = list[str](r.name for r in query.policy.commons()) 29 | 30 | super().__init__(title, query, attrname, completion, VALIDATE_EXACT, 31 | enable_regex=enable_regex, required=required, parent=parent, 32 | options_placement=options_placement) 33 | 34 | 35 | if __name__ == '__main__': 36 | import sys 37 | import logging 38 | import warnings 39 | 40 | logging.basicConfig(level=logging.DEBUG, 41 | format='%(asctime)s|%(levelname)s|%(name)s|%(message)s') 42 | warnings.simplefilter("default") 43 | 44 | q = setools.CommonQuery(setools.SELinuxPolicy()) 45 | 46 | app = QtWidgets.QApplication(sys.argv) 47 | mw = QtWidgets.QMainWindow() 48 | widget = CommonName("Test Common", q, "name", parent=mw) 49 | widget.setToolTip("test tooltip") 50 | widget.setWhatsThis("test what's this") 51 | mw.setCentralWidget(widget) 52 | mw.resize(widget.size()) 53 | whatsthis = QtWidgets.QWhatsThis.createAction(mw) 54 | mw.menuBar().addAction(whatsthis) # type: ignore[union-attr] 55 | mw.setStatusBar(QtWidgets.QStatusBar(mw)) 56 | mw.show() 57 | sys.exit(app.exec()) 58 | -------------------------------------------------------------------------------- /setoolsgui/widgets/criteria/constraintype.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: LGPL-2.1-only 2 | 3 | from PyQt6 import QtWidgets 4 | import setools 5 | 6 | from .checkboxset import CheckboxSetWidget 7 | 8 | DEFAULT_CHECKED = ("constrain",) 9 | 10 | __all__ = ('ConstrainType',) 11 | 12 | 13 | class ConstrainType(CheckboxSetWidget): 14 | 15 | """ 16 | Criteria selection widget presenting type enforcement rule types as a series 17 | of checkboxes. The selected checkboxes are then merged into a single Python 18 | list consisting of object names (constraint types) and stored in the query's 19 | specified attribute. 20 | """ 21 | 22 | def __init__(self, title: str, query: setools.PolicyQuery, attrname: str = "ruletype", 23 | parent: QtWidgets.QWidget | None = None) -> None: 24 | 25 | super().__init__(title, query, attrname, (rt.name for rt in setools.ConstraintRuletype), 26 | num_cols=2, parent=parent) 27 | 28 | for name, widget in self.criteria.items(): 29 | widget.setChecked(name in DEFAULT_CHECKED) 30 | widget.setToolTip(f"Match {name} rules.") 31 | widget.setWhatsThis( 32 | f""" 33 |

Match {name} rules

34 | 35 |

If a rule has the {name} rule type, it will be returned.

36 | """) 37 | 38 | 39 | if __name__ == '__main__': 40 | import sys 41 | import logging 42 | import warnings 43 | 44 | logging.basicConfig(level=logging.DEBUG, 45 | format='%(asctime)s|%(levelname)s|%(name)s|%(message)s') 46 | warnings.simplefilter("default") 47 | 48 | q = setools.ConstraintQuery(setools.SELinuxPolicy()) 49 | 50 | app = QtWidgets.QApplication(sys.argv) 51 | mw = QtWidgets.QMainWindow() 52 | w = ConstrainType("Test constrain ruletypes", q, parent=mw) 53 | w.setToolTip("test tooltip") 54 | w.setWhatsThis("test what's this") 55 | mw.setCentralWidget(w) 56 | mw.resize(w.size()) 57 | whatsthis = QtWidgets.QWhatsThis.createAction(mw) 58 | mw.menuBar().addAction(whatsthis) # type: ignore[union-attr] 59 | mw.show() 60 | rc = app.exec() 61 | sys.exit(rc) 62 | -------------------------------------------------------------------------------- /setoolsgui/widgets/criteria/criteria.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: LGPL-2.1-only 2 | 3 | import enum 4 | import logging 5 | import typing 6 | 7 | from PyQt6 import QtWidgets 8 | 9 | __all__ = ('CriteriaWidget', 'OptionsPlacement') 10 | 11 | 12 | class OptionsPlacement(enum.Enum): 13 | 14 | """Enumeration of options placement relative to the primary criteria widget (eg line edit).""" 15 | 16 | RIGHT = enum.auto() 17 | BELOW = enum.auto() 18 | 19 | 20 | class CriteriaWidget(QtWidgets.QGroupBox): 21 | 22 | """Base class for criteria widgets.""" 23 | 24 | def __init__(self, title: str, query, attrname: str, 25 | parent: QtWidgets.QWidget | None = None) -> None: 26 | 27 | super().__init__(parent=parent) 28 | self.log: typing.Final = logging.getLogger(self.__module__) 29 | self.query: typing.Final = query 30 | self.attrname: typing.Final[str] = attrname 31 | 32 | self.setTitle(title) 33 | 34 | @property 35 | def has_errors(self) -> bool: 36 | """ 37 | Get error state of this widget. 38 | 39 | If the error text is set, there is an error. 40 | """ 41 | raise NotImplementedError 42 | 43 | # 44 | # Overridden methods for typing purposes 45 | # 46 | # @typing.override 47 | def style(self) -> QtWidgets.QStyle: 48 | """Type-narrowed style() method. Always returns a QStyle.""" 49 | style = super().style() 50 | assert style, "No style set, this is an SETools bug" # type narrowing 51 | return style 52 | 53 | # 54 | # Save/Load field 55 | # 56 | 57 | def save(self, settings: dict) -> None: 58 | """Save the widget settings to the settings dictionary.""" 59 | raise NotImplementedError 60 | 61 | def load(self, settings: dict) -> None: 62 | """Load the widget settings from the settings dictionary.""" 63 | raise NotImplementedError 64 | -------------------------------------------------------------------------------- /setoolsgui/widgets/criteria/defaultruletype.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: LGPL-2.1-only 2 | import typing 3 | 4 | from PyQt6 import QtWidgets 5 | import setools 6 | 7 | from .checkboxset import CheckboxSetWidget 8 | 9 | # Checked by default: 10 | DEFAULT_CHECKED: typing.Final[tuple[str, ...]] = ("default_user", "default_role", 11 | "default_type", "default_range") 12 | 13 | __all__ = ('DefaultRuleType',) 14 | 15 | 16 | class DefaultRuleType(CheckboxSetWidget): 17 | 18 | """ 19 | Criteria selection widget presenting default_* rule types as a series 20 | of checkboxes. The selected checkboxes are then merged into a single Python 21 | list consisting of object names (default ruletypes) and stored in the query's 22 | specified attribute. 23 | """ 24 | 25 | def __init__(self, title: str, query: setools.PolicyQuery, attrname: str = "ruletype", 26 | parent: QtWidgets.QWidget | None = None) -> None: 27 | 28 | super().__init__(title, query, attrname, (rt.name for rt in setools.DefaultRuletype), 29 | num_cols=2, parent=parent) 30 | 31 | for name, widget in self.criteria.items(): 32 | widget.setChecked(name in DEFAULT_CHECKED) 33 | widget.setToolTip(f"Match {name} rules.") 34 | widget.setWhatsThis( 35 | f""" 36 |

Match {name} rules

37 | 38 |

If a rule has the {name} rule type, it will be returned.

39 | """) 40 | 41 | 42 | if __name__ == '__main__': 43 | import sys 44 | import warnings 45 | import pprint 46 | import logging 47 | 48 | logging.basicConfig(level=logging.DEBUG, 49 | format='%(asctime)s|%(levelname)s|%(name)s|%(message)s') 50 | warnings.simplefilter("default") 51 | 52 | q = setools.DefaultQuery(setools.SELinuxPolicy()) 53 | 54 | app = QtWidgets.QApplication(sys.argv) 55 | mw = QtWidgets.QMainWindow() 56 | w = DefaultRuleType("Test default ruletypes", q, parent=mw) 57 | w.setToolTip("test tooltip") 58 | w.setWhatsThis("test what's this") 59 | mw.setCentralWidget(w) 60 | mw.resize(w.size()) 61 | whatsthis = QtWidgets.QWhatsThis.createAction(mw) 62 | mw.menuBar().addAction(whatsthis) # type: ignore[union-attr] 63 | mw.show() 64 | rc = app.exec() 65 | print("Ruletypes set in query:") 66 | pprint.pprint(q.ruletype) 67 | sys.exit(rc) 68 | -------------------------------------------------------------------------------- /setoolsgui/widgets/criteria/fsuseruletype.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: LGPL-2.1-only 2 | import typing 3 | 4 | from PyQt6 import QtWidgets 5 | import setools 6 | 7 | from .checkboxset import CheckboxSetWidget 8 | 9 | DEFAULT_CHECKED: typing.Final[tuple[str, ...]] = ("fs_use_xattr",) 10 | 11 | __all__ = ('FSUseRuletype',) 12 | 13 | 14 | class FSUseRuletype(CheckboxSetWidget): 15 | 16 | """ 17 | Criteria selection widget presenting type enforcement rule types as a series 18 | of checkboxes. The selected checkboxes are then merged into a single Python 19 | list consisting of object names (fs_use_* ruletypes) and stored in the query's 20 | specified attribute. 21 | """ 22 | 23 | def __init__(self, title: str, query: setools.PolicyQuery, attrname: str = "ruletype", 24 | parent: QtWidgets.QWidget | None = None) -> None: 25 | 26 | super().__init__(title, query, attrname, (rt.name for rt in setools.FSUseRuletype), 27 | num_cols=2, parent=parent) 28 | 29 | for name, widget in self.criteria.items(): 30 | widget.setChecked(name in DEFAULT_CHECKED) 31 | widget.setToolTip(f"Match {name} rules.") 32 | widget.setWhatsThis( 33 | f""" 34 |

Match {name} rules

35 | 36 |

If a rule has the {name} rule type, it will be returned.

37 | """) 38 | 39 | 40 | if __name__ == '__main__': 41 | import sys 42 | import logging 43 | import warnings 44 | 45 | logging.basicConfig(level=logging.DEBUG, 46 | format='%(asctime)s|%(levelname)s|%(name)s|%(message)s') 47 | warnings.simplefilter("default") 48 | 49 | q = setools.FSUseQuery(setools.SELinuxPolicy()) 50 | 51 | app = QtWidgets.QApplication(sys.argv) 52 | mw = QtWidgets.QMainWindow() 53 | w = FSUseRuletype("Test fs_use_* ruletypes", q, parent=mw) 54 | w.setToolTip("test tooltip") 55 | w.setWhatsThis("test what's this") 56 | mw.setCentralWidget(w) 57 | mw.resize(w.size()) 58 | whatsthis = QtWidgets.QWhatsThis.createAction(mw) 59 | mw.menuBar().addAction(whatsthis) # type: ignore[union-attr] 60 | mw.show() 61 | rc = app.exec() 62 | sys.exit(rc) 63 | -------------------------------------------------------------------------------- /setoolsgui/widgets/criteria/mls.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: LGPL-2.1-only 2 | import typing 3 | 4 | from PyQt6 import QtWidgets 5 | 6 | from .name import NameWidget 7 | 8 | # Regex for exact matches 9 | CAT_VALIDATE_EXACT: typing.Final[str] = r"[A-Za-z0-9._-]*" 10 | SEN_VALIDATE_EXACT: typing.Final[str] = r"[A-Za-z0-9._-]*" 11 | 12 | __all__: typing.Final[tuple[str, ...]] = ("CategoryName", "SensitivityName") 13 | 14 | 15 | class CategoryName(NameWidget): 16 | 17 | """ 18 | Widget providing a QLineEdit for the user to enter a category name, with 19 | the criteria saved to the attributes of the specified query. 20 | """ 21 | 22 | def __init__(self, title: str, query, attrname: str, 23 | parent: QtWidgets.QWidget | None = None, 24 | enable_regex: bool = True, required: bool = False): 25 | 26 | completion: list[str] = sorted(b.name for b in query.policy.categories()) 27 | 28 | super().__init__(title, query, attrname, completion, CAT_VALIDATE_EXACT, 29 | enable_regex=enable_regex, required=required, parent=parent) 30 | 31 | 32 | class SensitivityName(NameWidget): 33 | 34 | """ 35 | Widget providing a QLineEdit for the user to enter a sensitivity name, with 36 | the criteria saved to the attributes of the specified query. 37 | """ 38 | 39 | def __init__(self, title: str, query, attrname: str, 40 | parent: QtWidgets.QWidget | None = None, 41 | enable_regex: bool = True, required: bool = False): 42 | 43 | completion: list[str] = sorted(b.name for b in query.policy.sensitivities()) 44 | 45 | super().__init__(title, query, attrname, completion, SEN_VALIDATE_EXACT, 46 | enable_regex=enable_regex, required=required, parent=parent) 47 | -------------------------------------------------------------------------------- /setoolsgui/widgets/criteria/mlsruletype.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: LGPL-2.1-only 2 | 3 | from PyQt6 import QtWidgets 4 | import setools 5 | 6 | from .checkboxset import CheckboxSetWidget 7 | 8 | DEFAULT_CHECKED = ("range_transition",) 9 | 10 | __all__ = ('MLSRuleType',) 11 | 12 | 13 | class MLSRuleType(CheckboxSetWidget): 14 | 15 | """ 16 | Criteria selection widget presenting type enforcement rule types as a series 17 | of checkboxes. The selected checkboxes are then merged into a single Python 18 | list consisting of object names (TE ruletypes) and stored in the query's 19 | specified attribute. 20 | """ 21 | 22 | def __init__(self, title: str, query: setools.PolicyQuery, attrname: str = "ruletype", 23 | parent: QtWidgets.QWidget | None = None) -> None: 24 | 25 | super().__init__(title, query, attrname, (rt.name for rt in setools.MLSRuletype), 26 | num_cols=1, parent=parent) 27 | 28 | for name, widget in self.criteria.items(): 29 | widget.setChecked(name in DEFAULT_CHECKED) 30 | widget.setToolTip(f"Match {name} rules.") 31 | widget.setWhatsThis( 32 | f""" 33 |

Match {name} rules

34 | 35 |

If a rule has the {name} rule type, it will be returned.

36 | """) 37 | 38 | 39 | if __name__ == '__main__': 40 | import sys 41 | import logging 42 | import pprint 43 | import warnings 44 | 45 | logging.basicConfig(level=logging.DEBUG, 46 | format='%(asctime)s|%(levelname)s|%(name)s|%(message)s') 47 | warnings.simplefilter("default") 48 | 49 | q = setools.MLSRuleQuery(setools.SELinuxPolicy()) 50 | 51 | app = QtWidgets.QApplication(sys.argv) 52 | mw = QtWidgets.QMainWindow() 53 | w = MLSRuleType("Test MLS ruletypes", q, parent=mw) 54 | w.setToolTip("test tooltip") 55 | w.setWhatsThis("test what's this") 56 | mw.setCentralWidget(w) 57 | mw.resize(w.size()) 58 | whatsthis = QtWidgets.QWhatsThis.createAction(mw) 59 | mw.menuBar().addAction(whatsthis) # type: ignore[union-attr] 60 | mw.show() 61 | rc = app.exec() 62 | print("Ruletypes set in query:") 63 | pprint.pprint(q.ruletype) 64 | print("range_trans enabled?", w.criteria["range_transition"].isEnabled()) 65 | sys.exit(rc) 66 | -------------------------------------------------------------------------------- /setoolsgui/widgets/criteria/rbacruletype.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: LGPL-2.1-only 2 | 3 | from PyQt6 import QtWidgets 4 | import setools 5 | 6 | from .checkboxset import CheckboxSetWidget 7 | 8 | # Checked by default: 9 | DEFAULT_CHECKED = ("allow",) 10 | 11 | __all__ = ('RBACRuleType',) 12 | 13 | 14 | class RBACRuleType(CheckboxSetWidget): 15 | 16 | """ 17 | Criteria selection widget presenting type enforcement rule types as a series 18 | of checkboxes. The selected checkboxes are then merged into a single Python 19 | list consisting of object names (TE ruletypes) and stored in the query's 20 | specified attribute. 21 | """ 22 | 23 | def __init__(self, title: str, query: setools.PolicyQuery, attrname: str = "ruletype", 24 | parent: QtWidgets.QWidget | None = None) -> None: 25 | 26 | super().__init__(title, query, attrname, (rt.name for rt in setools.RBACRuletype), 27 | num_cols=1, parent=parent) 28 | 29 | for name, widget in self.criteria.items(): 30 | widget.setChecked(name in DEFAULT_CHECKED) 31 | widget.setToolTip(f"Match {name} rules.") 32 | widget.setWhatsThis( 33 | f""" 34 |

Match {name} rules

35 | 36 |

If a rule has the {name} rule type, it will be returned.

37 | """) 38 | 39 | 40 | if __name__ == '__main__': 41 | import sys 42 | import warnings 43 | import pprint 44 | import logging 45 | 46 | logging.basicConfig(level=logging.DEBUG, 47 | format='%(asctime)s|%(levelname)s|%(name)s|%(message)s') 48 | warnings.simplefilter("default") 49 | 50 | q = setools.RBACRuleQuery(setools.SELinuxPolicy()) 51 | 52 | app = QtWidgets.QApplication(sys.argv) 53 | mw = QtWidgets.QMainWindow() 54 | w = RBACRuleType("Test RBAC ruletypes", q, parent=mw) 55 | w.setToolTip("test tooltip") 56 | w.setWhatsThis("test what's this") 57 | mw.setCentralWidget(w) 58 | mw.resize(w.size()) 59 | whatsthis = QtWidgets.QWhatsThis.createAction(mw) 60 | mw.menuBar().addAction(whatsthis) # type: ignore[union-attr] 61 | mw.show() 62 | rc = app.exec() 63 | print("Ruletypes set in query:") 64 | pprint.pprint(q.ruletype) 65 | sys.exit(rc) 66 | -------------------------------------------------------------------------------- /setoolsgui/widgets/criteria/typeattr.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: LGPL-2.1-only 2 | 3 | import typing 4 | 5 | from PyQt6 import QtWidgets 6 | import setools 7 | 8 | from .. import models 9 | from .criteria import OptionsPlacement 10 | from .list import ListWidget 11 | from .name import NameWidget 12 | 13 | # Regex for exact matches to types/attrs 14 | VALIDATE_EXACT: typing.Final[str] = r"[A-Za-z0-9._-]*" 15 | 16 | __all__ = ('TypeAttributeList', 'TypeAttributeName',) 17 | 18 | 19 | class TypeAttributeList(ListWidget): 20 | 21 | """A widget providing a QListView widget for selecting the type attributes.""" 22 | 23 | def __init__(self, title: str, query: setools.PolicyQuery, attrname: str, 24 | enable_equal: bool = False, enable_subset: bool = False, 25 | parent: QtWidgets.QWidget | None = None) -> None: 26 | 27 | model = models.TypeAttributeTable(data=sorted(query.policy.typeattributes())) 28 | 29 | super().__init__(title, query, attrname, model, enable_equal=enable_equal, 30 | enable_subset=enable_subset, parent=parent) 31 | 32 | self.criteria_any.setToolTip("Any selected type will match.") 33 | self.criteria_any.setWhatsThis("Any selected type will match.") 34 | 35 | 36 | class TypeAttributeName(NameWidget): 37 | 38 | """ 39 | Widget providing a QLineEdit that saves the input to the attributes 40 | of the specified query. This supports inputs of type attributes. 41 | """ 42 | 43 | def __init__(self, title: str, query: setools.PolicyQuery, attrname: str, /, *, 44 | parent: QtWidgets.QWidget | None = None, 45 | options_placement: OptionsPlacement = OptionsPlacement.RIGHT, 46 | enable_regex: bool = False, required: bool = False): 47 | 48 | # Create completion list 49 | completion = list[str](t.name for t in query.policy.typeattributes()) 50 | 51 | super().__init__(title, query, attrname, completion, VALIDATE_EXACT, 52 | enable_regex=enable_regex, required=required, 53 | options_placement=options_placement, parent=parent) 54 | -------------------------------------------------------------------------------- /setoolsgui/widgets/criteria/user.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: LGPL-2.1-only 2 | 3 | from PyQt6 import QtCore, QtWidgets 4 | import setools 5 | 6 | from .criteria import OptionsPlacement 7 | from .name import NameWidget 8 | 9 | # Regex for exact matches to roles 10 | VALIDATE_EXACT = r"[A-Za-z0-9._-]*" 11 | 12 | __all__ = ("UserName",) 13 | 14 | 15 | class UserName(NameWidget): 16 | 17 | """ 18 | Widget providing a QLineEdit that saves the input to the attributes 19 | of the specified query. This supports inputs of users. 20 | """ 21 | 22 | indirect_toggled = QtCore.pyqtSignal(bool) 23 | 24 | def __init__(self, title: str, query: setools.PolicyQuery, attrname: str, /, *, 25 | parent: QtWidgets.QWidget | None = None, 26 | options_placement: OptionsPlacement = OptionsPlacement.RIGHT, 27 | required: bool = False, enable_regex: bool = True): 28 | 29 | # Create completion list 30 | completion = list[str](u.name for u in query.policy.users()) 31 | 32 | super().__init__(title, query, attrname, completion, VALIDATE_EXACT, 33 | enable_regex=enable_regex, required=required, 34 | options_placement=options_placement, parent=parent) 35 | 36 | 37 | if __name__ == '__main__': 38 | import sys 39 | import logging 40 | import warnings 41 | 42 | logging.basicConfig(level=logging.DEBUG, 43 | format='%(asctime)s|%(levelname)s|%(name)s|%(message)s') 44 | warnings.simplefilter("default") 45 | 46 | q = setools.PortconQuery(setools.SELinuxPolicy()) 47 | 48 | app = QtWidgets.QApplication(sys.argv) 49 | mw = QtWidgets.QMainWindow() 50 | widget = UserName("Test user", q, "user", parent=mw) 51 | widget.setToolTip("test tooltip") 52 | widget.setWhatsThis("test what's this") 53 | mw.setCentralWidget(widget) 54 | mw.resize(widget.size()) 55 | whatsthis = QtWidgets.QWhatsThis.createAction(mw) 56 | mw.menuBar().addAction(whatsthis) # type: ignore[union-attr] 57 | mw.setStatusBar(QtWidgets.QStatusBar(mw)) 58 | mw.show() 59 | sys.exit(app.exec()) 60 | -------------------------------------------------------------------------------- /setoolsgui/widgets/details/__init__.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: LGPL-2.1-only 2 | 3 | from .boolean import * 4 | from .common import * 5 | from .context import * 6 | from .objclass import * 7 | from .role import * 8 | from .typeattr import * 9 | from .type import * 10 | from .user import * 11 | -------------------------------------------------------------------------------- /setoolsgui/widgets/details/boolean.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: LGPL-2.1-only 2 | 3 | from PyQt6 import QtGui, QtWidgets 4 | import setools 5 | 6 | from . import util 7 | 8 | __all__ = ('boolean_detail', 'boolean_detail_action', 'boolean_tooltip') 9 | 10 | 11 | def boolean_detail(boolean: setools.Boolean, parent: QtWidgets.QWidget | None = None) -> None: 12 | """Display a dialog with Boolean details.""" 13 | 14 | util.display_object_details( 15 | f"{boolean} Details", 16 | f""" 17 |

Boolean Name

18 |

{boolean}

19 | 20 |

Default state: {boolean.state}

21 | """, 22 | parent) 23 | 24 | 25 | def boolean_detail_action(boolean: setools.Boolean, 26 | parent: QtWidgets.QWidget | None = None) -> QtGui.QAction: 27 | """Return a QAction that, when triggered, opens a Boolean detail popup.""" 28 | a = QtGui.QAction(f"Properties of {boolean}") 29 | a.triggered.connect(lambda x: boolean_detail(boolean, parent)) 30 | return a 31 | 32 | 33 | def boolean_tooltip(boolean: setools.Boolean) -> str: 34 | """Return tooltip text for this Boolean.""" 35 | return f"{boolean} is a Boolean with {boolean.state} default state." 36 | -------------------------------------------------------------------------------- /setoolsgui/widgets/details/common.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: LGPL-2.1-only 2 | from PyQt6 import QtGui, QtWidgets 3 | import setools 4 | 5 | from . import util 6 | 7 | __all__ = ('common_detail', 'common_detail_action', 'common_tooltip') 8 | 9 | 10 | def common_detail(common: setools.Common, parent: QtWidgets.QWidget | None = None) -> None: 11 | """Display a dialog with common details.""" 12 | 13 | util.display_object_details( 14 | f"{common} Details", 15 | f""" 16 |

Common Name

17 |

{common}

18 | 19 |

Permissions ({len(common.perms)})

20 |
    21 | {"".join(f"
  • {p}
  • " for p in sorted(common.perms))} 22 |
23 | """, 24 | parent) 25 | 26 | 27 | def common_detail_action(common: setools.Common, 28 | parent: QtWidgets.QWidget | None = None) -> QtGui.QAction: 29 | """Return a QAction that, when triggered, opens a common detail popup.""" 30 | a = QtGui.QAction(f"Properties of {common}") 31 | a.triggered.connect(lambda x: common_detail(common, parent)) 32 | return a 33 | 34 | 35 | def common_tooltip(common: setools.Common) -> str: 36 | """Return tooltip text for this common.""" 37 | nperms = len(common.perms) 38 | if nperms == 0: 39 | return f"{common} is a common permission set with no permissions defined." 40 | elif nperms > 5: 41 | return f"{common} is a common permission set with {nperms} permissions defined." 42 | else: 43 | return f"{common} is a common permission set with permissions: " \ 44 | f"{', '.join(common.perms)}" 45 | -------------------------------------------------------------------------------- /setoolsgui/widgets/details/context.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: LGPL-2.1-only 2 | 3 | from PyQt6 import QtGui, QtWidgets 4 | import setools 5 | 6 | from .role import role_detail_action 7 | from .type import type_detail_action 8 | from .user import user_detail_action 9 | 10 | __all__ = ("context_detail_action",) 11 | 12 | 13 | def context_detail_action(context: setools.Context, 14 | parent: QtWidgets.QWidget | None = None) -> tuple[QtGui.QAction, 15 | QtGui.QAction, 16 | QtGui.QAction]: 17 | 18 | """Return a tuple of QActions that, when triggered, opens a detail popup for the context.""" 19 | return (user_detail_action(context.user, parent), 20 | role_detail_action(context.role, parent), 21 | type_detail_action(context.type_, parent)) 22 | -------------------------------------------------------------------------------- /setoolsgui/widgets/details/role.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: LGPL-2.1-only 2 | 3 | from PyQt6 import QtGui, QtWidgets 4 | import setools 5 | 6 | from . import util 7 | 8 | __all__ = ("role_detail", "role_detail_action", "role_tooltip") 9 | 10 | 11 | def role_detail(role: setools.Role, parent: QtWidgets.QWidget | None = None) -> None: 12 | 13 | """Display a dialog with role details.""" 14 | 15 | types = list[setools.Type](sorted(role.types())) 16 | 17 | util.display_object_details( 18 | f"{role} Details", 19 | f""" 20 |

Role Name

21 |

{role}

22 | 23 |

Types ({len(types)})

24 |
    25 | {"".join(f"
  • {t}
  • " for t in types)} 26 |
27 | """, 28 | parent) 29 | 30 | 31 | def role_detail_action(role: setools.Role, 32 | parent: QtWidgets.QWidget | None = None) -> QtGui.QAction: 33 | 34 | """Return a QAction that, when triggered, opens an detail popup for role.""" 35 | 36 | a = QtGui.QAction(f"Properties of {role}") 37 | a.triggered.connect(lambda x: role_detail(role, parent)) 38 | return a 39 | 40 | 41 | def role_tooltip(role: setools.Role) -> str: 42 | """Return tooltip text for this role.""" 43 | n_types = len(list(role.types())) 44 | if n_types == 0: 45 | return f"{role} is a role with no type associations." 46 | elif n_types > 5: 47 | return f"{role} is a role associated with {n_types} types." 48 | else: 49 | return f"{role} is a role associated with types: " \ 50 | f"{', '.join(t.name for t in role.types())}" 51 | -------------------------------------------------------------------------------- /setoolsgui/widgets/details/typeattr.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: LGPL-2.1-only 2 | 3 | from PyQt6 import QtGui, QtWidgets 4 | import setools 5 | 6 | from . import util 7 | 8 | __all__ = ('typeattr_detail', 'typeattr_detail_action', 'typeattr_tooltip') 9 | 10 | 11 | def typeattr_detail(attr: setools.TypeAttribute, parent: QtWidgets.QWidget | None = None) -> None: 12 | 13 | """Display a dialog with type attribute details.""" 14 | 15 | types = list[setools.Type](sorted(attr.expand())) 16 | 17 | util.display_object_details( 18 | f"{attr} Details", 19 | f""" 20 |

Type Attribute Name

21 |

{attr}

22 | 23 |

Types ({len(types)})

24 |
    25 | {"".join(f"
  • {t}
  • " for t in types)} 26 |
27 | """, 28 | parent) 29 | 30 | 31 | def typeattr_detail_action(attr: setools.TypeAttribute, 32 | parent: QtWidgets.QWidget | None = None) -> QtGui.QAction: 33 | 34 | """Return a QAction that, when triggered, opens an detail popup for the attr.""" 35 | 36 | a = QtGui.QAction(f"Properties of {attr}") 37 | a.triggered.connect(lambda _: typeattr_detail(attr, parent)) 38 | return a 39 | 40 | 41 | def typeattr_tooltip(attr: setools.TypeAttribute) -> str: 42 | """Return tooltip text for this type attribute.""" 43 | n_types = len(attr) 44 | if n_types == 0: 45 | return f"{attr.name} is an empty attribute." 46 | elif n_types > 5: 47 | return f"{attr.name} is an attribute consisting of {n_types} types." 48 | else: 49 | return f"{attr.name} is an attribute consisting of: " \ 50 | f"{', '.join(t.name for t in attr.expand())}" 51 | -------------------------------------------------------------------------------- /setoolsgui/widgets/details/user.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: LGPL-2.1-only 2 | 3 | from PyQt6 import QtGui, QtWidgets 4 | import setools 5 | 6 | from . import util 7 | 8 | __all__ = ("user_detail", "user_detail_action", "user_tooltip") 9 | 10 | 11 | def user_detail(user: setools.User, parent: QtWidgets.QWidget | None = None) -> None: 12 | 13 | """Display a dialog with user details.""" 14 | 15 | roles = list[setools.Role](sorted(user.roles)) 16 | 17 | try: 18 | mlsinfo = f""" 19 |

MLS Default Level

20 |

{user.mls_level}

21 | 22 |

MLS Range

23 |

{user.mls_range}

24 | """ 25 | 26 | except setools.exception.MLSDisabled: 27 | mlsinfo = "" 28 | 29 | util.display_object_details( 30 | f"{user} Details", 31 | f""" 32 |

User Name

33 |

{user}

34 | 35 |

Roles ({len(roles)})

36 |
    37 | {"".join(f"
  • {t}
  • " for t in roles)} 38 |
39 | {mlsinfo} 40 | """, 41 | parent) 42 | 43 | 44 | def user_detail_action(user: setools.User, 45 | parent: QtWidgets.QWidget | None = None) -> QtGui.QAction: 46 | 47 | """Return a QAction that, when triggered, opens an detail popup for user.""" 48 | 49 | a = QtGui.QAction(f"Properties of {user}") 50 | a.triggered.connect(lambda x: user_detail(user, parent)) 51 | return a 52 | 53 | 54 | def user_tooltip(user: setools.User) -> str: 55 | """Return tooltip text for this user.""" 56 | n_roles = len(user.roles) 57 | if n_roles == 0: 58 | return f"{user} is a user with no type associations." 59 | elif n_roles > 5: 60 | return f"{user} is a user associated with {n_roles} types." 61 | else: 62 | return f"{user} is a user associated with types: " \ 63 | f"{', '.join(r.name for r in user.roles)}" 64 | -------------------------------------------------------------------------------- /setoolsgui/widgets/details/util.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: LGPL-2.1-only 2 | 3 | from PyQt6 import QtCore, QtWidgets 4 | 5 | __all__ = ("display_object_details", ) 6 | 7 | 8 | def display_object_details(title: str, html_text: str, 9 | parent: QtWidgets.QWidget | None = None) -> None: 10 | 11 | """Display a non-modal dialog box with information in HTML.""" 12 | 13 | if parent is None: 14 | # details requests from models can't provide a parent widget, afaict 15 | # suppress mypy error: "QCoreApplication" has no attribute "focusWidget" 16 | parent = QtWidgets.QApplication.instance().focusWidget() # type: ignore 17 | 18 | popup = QtWidgets.QDialog(parent=parent) 19 | popup.setWindowTitle(title) 20 | popup.setObjectName("details_popup") 21 | popup.setModal(False) 22 | 23 | layout = QtWidgets.QVBoxLayout(popup) 24 | 25 | contents = QtWidgets.QTextBrowser(popup) 26 | contents.setObjectName("details_contents") 27 | contents.setHtml(html_text) 28 | contents.setReadOnly(True) 29 | layout.addWidget(contents) 30 | 31 | buttonBox = QtWidgets.QDialogButtonBox(popup) 32 | buttonBox.setOrientation(QtCore.Qt.Orientation.Horizontal) 33 | buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.StandardButton.Close) 34 | buttonBox.clicked.connect(popup.close) 35 | layout.addWidget(buttonBox) 36 | 37 | QtCore.QMetaObject.connectSlotsByName(popup) 38 | popup.show() 39 | -------------------------------------------------------------------------------- /setoolsgui/widgets/exception.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016, Chris PeBenito 2 | # 3 | # SPDX-License-Identifier: LGPL-2.1-only 4 | # 5 | # 6 | 7 | 8 | class TabFieldError(RuntimeError): 9 | 10 | """Exception when trying to save a tab that has errors.""" 11 | pass 12 | -------------------------------------------------------------------------------- /setoolsgui/widgets/models/__init__.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: LGPL-2.1-only 2 | 3 | from . import typing 4 | 5 | from .boolean import * 6 | from .bounds import * 7 | from .common import * 8 | from .constraint import * 9 | from .default import * 10 | from .fsuse import * 11 | from .genfscon import * 12 | from .ibendportcon import * 13 | from .ibpkeycon import * 14 | from .initsid import * 15 | from .mls import * 16 | from .mlsrule import * 17 | from .modelroles import * 18 | from .netifcon import * 19 | from .nodecon import * 20 | from .objclass import * 21 | from .portcon import * 22 | from .rbacrule import * 23 | from .role import * 24 | from .table import * 25 | from .terule import * 26 | from .type import * 27 | from .typeattr import * 28 | from .user import * 29 | -------------------------------------------------------------------------------- /setoolsgui/widgets/models/boolean.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016, Tresys Technology, LLC 2 | # 3 | # SPDX-License-Identifier: LGPL-2.1-only 4 | # 5 | # 6 | from PyQt6 import QtCore 7 | import setools 8 | 9 | from .modelroles import ModelRoles 10 | from .table import SEToolsTableModel 11 | from .. import details 12 | 13 | __all__ = ("BooleanTable",) 14 | 15 | 16 | class BooleanTable(SEToolsTableModel[setools.Boolean]): 17 | 18 | """Table-based model for booleans.""" 19 | 20 | headers = ["Name", "Default State"] 21 | 22 | def data(self, index: QtCore.QModelIndex, role: int = ModelRoles.DisplayRole): 23 | if not self.item_list or not index.isValid(): 24 | return None 25 | 26 | row = index.row() 27 | col = index.column() 28 | boolean = self.item_list[row] 29 | 30 | match role: 31 | case ModelRoles.DisplayRole: 32 | match col: 33 | case 0: 34 | return boolean.name 35 | case 1: 36 | return str(boolean.state) 37 | 38 | case ModelRoles.ContextMenuRole: 39 | return (details.boolean_detail_action(boolean), ) 40 | 41 | case ModelRoles.ToolTipRole: 42 | return details.boolean_tooltip(boolean) 43 | 44 | return super().data(index, role) 45 | -------------------------------------------------------------------------------- /setoolsgui/widgets/models/bounds.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016, Tresys Technology, LLC 2 | # 3 | # SPDX-License-Identifier: LGPL-2.1-only 4 | # 5 | # 6 | from PyQt6 import QtCore 7 | import setools 8 | 9 | from .. import details 10 | from .modelroles import ModelRoles 11 | from .table import SEToolsTableModel 12 | 13 | __all__ = ("BoundsTable",) 14 | 15 | 16 | class BoundsTable(SEToolsTableModel[setools.Bounds]): 17 | 18 | """Table-based model for *bounds.""" 19 | 20 | headers = ["Rule Type", "Parent", "Child"] 21 | 22 | def data(self, index: QtCore.QModelIndex, role: int = ModelRoles.DisplayRole): 23 | if not self.item_list or not index.isValid(): 24 | return None 25 | 26 | row = index.row() 27 | col = index.column() 28 | item = self.item_list[row] 29 | 30 | match role: 31 | case ModelRoles.DisplayRole: 32 | match col: 33 | case 0: 34 | return item.ruletype.name 35 | case 1: 36 | return item.parent.name 37 | case 2: 38 | return item.child.name 39 | 40 | case ModelRoles.ContextMenuRole: 41 | match col: 42 | case 1: 43 | return (details.type_detail_action(item.parent),) 44 | case 2: 45 | return (details.type_detail_action(item.child),) 46 | 47 | case ModelRoles.WhatsThisRole: 48 | match col: 49 | case 0: 50 | column_whatsthis = "

This is the rule type.

" 51 | case 1: 52 | column_whatsthis = "

This is the parent/bounding type.

" 53 | case 2: 54 | column_whatsthis = "

This is the child/bounded type.

" 55 | case _: 56 | column_whatsthis = "" 57 | 58 | return \ 59 | f""" 60 |

Table Representation of bounds rules

61 | 62 |

Each part of the declaration is represented as a column in the table.

63 | 64 | {column_whatsthis} 65 | """ 66 | 67 | return super().data(index, role) 68 | -------------------------------------------------------------------------------- /setoolsgui/widgets/models/common.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016, Tresys Technology, LLC 2 | # 3 | # SPDX-License-Identifier: LGPL-2.1-only 4 | # 5 | # 6 | from PyQt6 import QtCore 7 | import setools 8 | 9 | from .modelroles import ModelRoles 10 | from .table import SEToolsTableModel 11 | from .. import details 12 | 13 | __all__ = ("CommonTable",) 14 | 15 | 16 | class CommonTable(SEToolsTableModel[setools.Common]): 17 | 18 | """Table-based model for common permission sets.""" 19 | 20 | headers = ["Name", "Permissions"] 21 | 22 | def data(self, index: QtCore.QModelIndex, role: int = ModelRoles.DisplayRole): 23 | if not self.item_list or not index.isValid(): 24 | return None 25 | 26 | row = index.row() 27 | col = index.column() 28 | item = self.item_list[row] 29 | 30 | match role: 31 | case ModelRoles.DisplayRole: 32 | match col: 33 | case 0: 34 | return item.name 35 | case 1: 36 | return ", ".join(sorted(item.perms)) 37 | 38 | case ModelRoles.ContextMenuRole: 39 | return (details.common_detail_action(item), ) 40 | 41 | case ModelRoles.ToolTipRole: 42 | return details.common_tooltip(item) 43 | 44 | return super().data(index, role) 45 | -------------------------------------------------------------------------------- /setoolsgui/widgets/models/fsuse.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016, Tresys Technology, LLC 2 | # 3 | # SPDX-License-Identifier: LGPL-2.1-only 4 | # 5 | # 6 | from PyQt6 import QtCore 7 | import setools 8 | 9 | from .. import details 10 | from .modelroles import ModelRoles 11 | from .table import SEToolsTableModel 12 | 13 | __all__ = ("FSUseTable",) 14 | 15 | 16 | class FSUseTable(SEToolsTableModel[setools.FSUse]): 17 | 18 | """Table-based model for fs_use_*.""" 19 | 20 | headers = ["Ruletype", "FS Type", "Context"] 21 | 22 | def data(self, index: QtCore.QModelIndex, role: int = ModelRoles.DisplayRole): 23 | if not self.item_list or not index.isValid(): 24 | return None 25 | 26 | row = index.row() 27 | col = index.column() 28 | rule = self.item_list[row] 29 | 30 | match role: 31 | case ModelRoles.DisplayRole: 32 | match col: 33 | case 0: 34 | return rule.ruletype.name 35 | case 1: 36 | return rule.fs 37 | case 2: 38 | return str(rule.context) 39 | 40 | case ModelRoles.ContextMenuRole: 41 | if col == 2: 42 | return details.context_detail_action(rule.context) 43 | 44 | case ModelRoles.WhatsThisRole: 45 | match col: 46 | case 0: 47 | column_whatsthis = \ 48 | """ 49 |

This is the statement type.

50 | """ 51 | case 1: 52 | column_whatsthis = \ 53 | """ 54 |

This is the type/name of the filesystem.

55 | """ 56 | case 2: 57 | column_whatsthis = \ 58 | """ 59 |

This is the context of the fs_use_*.

60 | """ 61 | case _: 62 | column_whatsthis = "" 63 | 64 | return \ 65 | f""" 66 |

Table Representation of fs_use_*

67 | 68 |

Each part of the rule is represented as a column in the table.

69 | 70 | {column_whatsthis} 71 | """ 72 | 73 | return super().data(index, role) 74 | -------------------------------------------------------------------------------- /setoolsgui/widgets/models/initsid.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016, Tresys Technology, LLC 2 | # 3 | # SPDX-License-Identifier: LGPL-2.1-only 4 | # 5 | # 6 | from PyQt6 import QtCore 7 | import setools 8 | 9 | from .. import details 10 | from .modelroles import ModelRoles 11 | from .table import SEToolsTableModel 12 | 13 | __all__ = ("InitialSIDTable",) 14 | 15 | 16 | class InitialSIDTable(SEToolsTableModel[setools.InitialSID]): 17 | 18 | """Table-based model for initial SIDs.""" 19 | 20 | headers = ["Name", "Context"] 21 | 22 | def data(self, index: QtCore.QModelIndex, role: int = ModelRoles.DisplayRole): 23 | if not self.item_list or not index.isValid(): 24 | return None 25 | 26 | row = index.row() 27 | col = index.column() 28 | rule = self.item_list[row] 29 | 30 | match role: 31 | case ModelRoles.DisplayRole: 32 | match col: 33 | case 0: 34 | return rule.name 35 | case 1: 36 | return str(rule.context) 37 | 38 | case ModelRoles.ContextMenuRole: 39 | if col == 1: 40 | return details.context_detail_action(rule.context) 41 | 42 | case ModelRoles.WhatsThisRole: 43 | match col: 44 | case 0: 45 | column_whatsthis = \ 46 | """ 47 |

This is the name of the initial context.

48 | """ 49 | case 1: 50 | column_whatsthis = \ 51 | """ 52 |

This is the context of the initial context.

53 | """ 54 | case _: 55 | column_whatsthis = "" 56 | 57 | return \ 58 | f""" 59 |

Table Representation of Initial Contexts (sid)

60 | 61 |

Each part of the rule is represented as a column in the table.

62 | 63 | {column_whatsthis} 64 | """ 65 | 66 | return super().data(index, role) 67 | -------------------------------------------------------------------------------- /setoolsgui/widgets/models/modelroles.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: LGPL-2.1-only 2 | import enum 3 | 4 | 5 | from PyQt6 import QtCore 6 | 7 | __all__ = ("ModelRoles",) 8 | 9 | 10 | class ModelRoles(enum.IntEnum): 11 | 12 | """ 13 | Roles for SETools models. 14 | 15 | The intent is to be a superset of QtCore.Qt.ItemDataRole, with 16 | additional custom roles for SETools models. 17 | 18 | https://doc.qt.io/qt-6/qt.html#ItemDataRole-enum 19 | """ 20 | 21 | # general purpose roles 22 | DisplayRole = QtCore.Qt.ItemDataRole.DisplayRole 23 | DecorationRole = QtCore.Qt.ItemDataRole.DecorationRole 24 | EditRole = QtCore.Qt.ItemDataRole.EditRole 25 | ToolTipRole = QtCore.Qt.ItemDataRole.ToolTipRole 26 | StatusTipRole = QtCore.Qt.ItemDataRole.StatusTipRole 27 | WhatsThisRole = QtCore.Qt.ItemDataRole.WhatsThisRole 28 | SizeHintRole = QtCore.Qt.ItemDataRole.SizeHintRole 29 | 30 | # appearance/metadata roles 31 | FontRole = QtCore.Qt.ItemDataRole.FontRole 32 | TextAlignmentRole = QtCore.Qt.ItemDataRole.TextAlignmentRole 33 | BackgroundRole = QtCore.Qt.ItemDataRole.BackgroundRole 34 | ForegroundRole = QtCore.Qt.ItemDataRole.ForegroundRole 35 | CheckStateRole = QtCore.Qt.ItemDataRole.CheckStateRole 36 | InitialSortOrderRole = QtCore.Qt.ItemDataRole.UserRole 37 | 38 | # accessibility roles 39 | AccessibleTextRole = QtCore.Qt.ItemDataRole.AccessibleTextRole 40 | AccessibleDescriptionRole = QtCore.Qt.ItemDataRole.AccessibleDescriptionRole 41 | 42 | # Custom roles 43 | PolicyObjRole = QtCore.Qt.ItemDataRole.UserRole 44 | ContextMenuRole = QtCore.Qt.ItemDataRole.UserRole + 1 45 | -------------------------------------------------------------------------------- /setoolsgui/widgets/models/nodecon.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016, Tresys Technology, LLC 2 | # 3 | # SPDX-License-Identifier: LGPL-2.1-only 4 | # 5 | # 6 | from PyQt6 import QtCore 7 | import setools 8 | 9 | from .. import details 10 | from .modelroles import ModelRoles 11 | from .table import SEToolsTableModel 12 | 13 | __all__ = ("NodeconTable",) 14 | 15 | 16 | class NodeconTable(SEToolsTableModel[setools.Nodecon]): 17 | 18 | """Table-based model for nodecons.""" 19 | 20 | headers = ["Network", "Context"] 21 | 22 | def data(self, index: QtCore.QModelIndex, role: int = ModelRoles.DisplayRole): 23 | if not self.item_list or not index.isValid(): 24 | return None 25 | 26 | row = index.row() 27 | col = index.column() 28 | rule = self.item_list[row] 29 | 30 | match role: 31 | case ModelRoles.DisplayRole: 32 | match col: 33 | case 0: 34 | return str(rule.network.with_netmask) 35 | case 1: 36 | return str(rule.context) 37 | 38 | case ModelRoles.ContextMenuRole: 39 | if col == 1: 40 | return details.context_detail_action(rule.context) 41 | 42 | case ModelRoles.WhatsThisRole: 43 | match col: 44 | case 0: 45 | column_whatsthis = \ 46 | """ 47 |

This is the network of the nodecon.

48 | """ 49 | case 2: 50 | column_whatsthis = \ 51 | """ 52 |

This is the context of the nodecon.

53 | """ 54 | case _: 55 | column_whatsthis = "" 56 | 57 | return \ 58 | f""" 59 |

Table Representation of Network Node Contexts (nodecon)

60 | 61 |

Each part of the rule is represented as a column in the table.

62 | 63 | {column_whatsthis} 64 | """ 65 | 66 | return super().data(index, role) 67 | -------------------------------------------------------------------------------- /setoolsgui/widgets/models/objclass.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016, Tresys Technology, LLC 2 | # 3 | # SPDX-License-Identifier: LGPL-2.1-only 4 | # 5 | # 6 | from itertools import chain 7 | 8 | from PyQt6 import QtCore 9 | import setools 10 | 11 | from .modelroles import ModelRoles 12 | from .table import SEToolsTableModel 13 | from .. import details 14 | 15 | __all__ = ("ObjClassTable",) 16 | 17 | 18 | class ObjClassTable(SEToolsTableModel[setools.ObjClass]): 19 | 20 | """Table-based model for object classes.""" 21 | 22 | headers = ["Name", "Permissions"] 23 | 24 | def data(self, index: QtCore.QModelIndex, role: int = ModelRoles.DisplayRole): 25 | if not self.item_list or not index.isValid(): 26 | return None 27 | 28 | row = index.row() 29 | col = index.column() 30 | item = self.item_list[row] 31 | 32 | match role: 33 | case ModelRoles.DisplayRole: 34 | match col: 35 | case 0: 36 | return item.name 37 | case 1: 38 | try: 39 | return ", ".join(sorted(chain(item.common.perms, item.perms))) 40 | except setools.exception.NoCommon: 41 | return ", ".join(sorted(item.perms)) 42 | 43 | case ModelRoles.ContextMenuRole: 44 | return (details.objclass_detail_action(item), ) 45 | 46 | case ModelRoles.ToolTipRole: 47 | return details.objclass_tooltip(item) 48 | 49 | return super().data(index, role) 50 | -------------------------------------------------------------------------------- /setoolsgui/widgets/models/role.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016, Tresys Technology, LLC 2 | # 3 | # SPDX-License-Identifier: LGPL-2.1-only 4 | # 5 | # 6 | from PyQt6 import QtCore 7 | import setools 8 | 9 | from .. import details 10 | from .modelroles import ModelRoles 11 | from .table import SEToolsTableModel 12 | 13 | __all__ = ("RoleTable",) 14 | 15 | 16 | class RoleTable(SEToolsTableModel[setools.Role]): 17 | 18 | """Table-based model for roles.""" 19 | 20 | headers = ["Name", "Types"] 21 | 22 | def data(self, index: QtCore.QModelIndex, role: int = ModelRoles.DisplayRole): 23 | if not self.item_list or not index.isValid(): 24 | return None 25 | 26 | # There are two roles here. 27 | # The parameter, role, is the Qt role 28 | # The below item is a role in the list. 29 | row = index.row() 30 | col = index.column() 31 | item = self.item_list[row] 32 | 33 | match role: 34 | case ModelRoles.DisplayRole: 35 | match col: 36 | case 0: 37 | return item.name 38 | case 1: 39 | return ", ".join(sorted(t.name for t in item.types())) 40 | 41 | case ModelRoles.ContextMenuRole: 42 | match col: 43 | case 0: 44 | return (details.role_detail_action(item),) 45 | case 1: 46 | return (details.type_detail_action(t) for t in sorted(item.types())) 47 | 48 | case ModelRoles.WhatsThisRole: 49 | match col: 50 | case 0: 51 | column_whatsthis = "

This is the name of the role.

" 52 | case 1: 53 | column_whatsthis = \ 54 | "

This is the list of types associated with this role.

" 55 | case _: 56 | column_whatsthis = "" 57 | 58 | return \ 59 | f""" 60 |

Table Representation of Roles

61 | 62 |

Each part of the declaration is represented as a column in the table.

63 | 64 | {column_whatsthis} 65 | """ 66 | 67 | return super().data(index, role) 68 | -------------------------------------------------------------------------------- /setoolsgui/widgets/models/typeattr.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016, Tresys Technology, LLC 2 | # 3 | # SPDX-License-Identifier: LGPL-2.1-only 4 | # 5 | # 6 | from PyQt6 import QtCore 7 | import setools 8 | 9 | from .. import details 10 | from .modelroles import ModelRoles 11 | from .table import SEToolsTableModel 12 | 13 | __all__ = ("TypeAttributeTable",) 14 | 15 | 16 | class TypeAttributeTable(SEToolsTableModel[setools.TypeAttribute]): 17 | 18 | """Table-based model for roles.""" 19 | 20 | headers = ["Name", "Types"] 21 | 22 | def data(self, index: QtCore.QModelIndex, role: int = ModelRoles.DisplayRole): 23 | if not self.item_list or not index.isValid(): 24 | return None 25 | 26 | row = index.row() 27 | col = index.column() 28 | attr = self.item_list[row] 29 | 30 | match role: 31 | case ModelRoles.DisplayRole: 32 | match col: 33 | case 0: 34 | return attr.name 35 | case 1: 36 | return ", ".join(sorted(a.name for a in sorted(attr.expand()))) 37 | 38 | case ModelRoles.ContextMenuRole: 39 | match col: 40 | case 0: 41 | return (details.typeattr_detail_action(attr),) 42 | case 1: 43 | return (details.type_detail_action(t) for t in sorted(attr.expand())) 44 | 45 | case ModelRoles.WhatsThisRole: 46 | match col: 47 | case 0: 48 | column_whatsthis = "

This is the name of the type attribute.

" 49 | case 1: 50 | column_whatsthis = \ 51 | "

This is the list of types associated with the attribute.

" 52 | case _: 53 | column_whatsthis = "" 54 | 55 | return \ 56 | f""" 57 |

Table Representation of SELinux users

58 | 59 |

Each part of the declaration is represented as a column in the table.

60 | 61 | {column_whatsthis} 62 | """ 63 | 64 | return super().data(index, role) 65 | -------------------------------------------------------------------------------- /setoolsgui/widgets/models/typing.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: LGPL-2.1-only 2 | 3 | import abc 4 | 5 | from PyQt6 import QtCore, QtGui 6 | 7 | # These are all of the return types for the standard QtCore.Qt.ItemDataRole roles 8 | # for the data method in the models. 9 | AllStdDataTypes = str | QtGui.QColor | QtGui.QIcon | QtGui.QPixmap | QtCore.QSize | \ 10 | QtGui.QFont | QtCore.Qt.AlignmentFlag | QtGui.QBrush | QtCore.Qt.CheckState | \ 11 | QtCore.Qt.SortOrder | None 12 | 13 | # This is the return type for the ModelRoles.ContextMenuRole role 14 | ContextMenuType = tuple[QtGui.QAction, ...] 15 | 16 | 17 | class MetaclassFix(type(QtCore.QObject), abc.ABC): # type: ignore[misc] 18 | 19 | """ 20 | Fix metaclass issues. 21 | 22 | Use this when doing a Generic[] with a PyQt type. Fixes this error: 23 | 24 | TypeError: metaclass conflict: the metaclass of a derived class must be a 25 | (non-strict) subclass of the metaclasses of all its bases 26 | """ 27 | pass 28 | -------------------------------------------------------------------------------- /setoolsgui/widgets/util.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: LGPL-2.1-only 2 | 3 | import logging 4 | import traceback 5 | import types 6 | import typing 7 | 8 | from PyQt6 import QtWidgets 9 | 10 | __all__: typing.Final[tuple[str, ...]] = ("QMessageOnException",) 11 | 12 | 13 | class QMessageOnException: 14 | 15 | """Context manager to display a message box on exception.""" 16 | 17 | def __init__(self, title: str, message: str, /, *, 18 | suppress: bool = True, 19 | log: logging.Logger | None = None, 20 | icon: QtWidgets.QMessageBox.Icon = QtWidgets.QMessageBox.Icon.Critical, 21 | parent: QtWidgets.QWidget | None = None) -> None: 22 | 23 | self.title: typing.Final[str] = title 24 | self.message: typing.Final[str] = message 25 | self.suppress: typing.Final[bool] = suppress 26 | self.parent: typing.Final[QtWidgets.QWidget | None] = parent 27 | self.log: typing.Final[logging.Logger] = log if log else logging.getLogger(__name__) 28 | self.icon: typing.Final[QtWidgets.QMessageBox.Icon] = icon 29 | 30 | def __enter__(self) -> None: 31 | pass 32 | 33 | def __exit__(self, 34 | exc_type: type[BaseException] | None, 35 | exc_value: BaseException | None, 36 | tb: types.TracebackType | None) -> bool: 37 | 38 | if exc_type: 39 | self.log.critical(self.message) 40 | self.log.debug("Backtrace", exc_info=True) 41 | 42 | msg = QtWidgets.QMessageBox(self.icon, 43 | self.title, 44 | self.message, 45 | parent=self.parent) 46 | 47 | msg.setInformativeText(str(exc_value)) 48 | msg.setDetailedText("\n".join(traceback.format_tb(tb))) 49 | msg.exec() 50 | 51 | return self.suppress 52 | 53 | return False 54 | -------------------------------------------------------------------------------- /setoolsgui/widgets/views/__init__.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: LGPL-2.1-only 2 | from .listview import * 3 | from .tableview import * 4 | from .treewidget import * 5 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SELinuxProject/setools/7503c647eecaead2fec30c938eadba0f4a01a69b/tests/__init__.py -------------------------------------------------------------------------------- /tests/gui/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SELinuxProject/setools/7503c647eecaead2fec30c938eadba0f4a01a69b/tests/gui/__init__.py -------------------------------------------------------------------------------- /tests/gui/conftest.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-2.0-only 2 | 3 | import os 4 | import pathlib 5 | import pytest 6 | 7 | try: 8 | import PyQt6 9 | have_pyqt6 = True 10 | except ImportError: 11 | have_pyqt6 = False 12 | 13 | try: 14 | import pytestqt 15 | have_pqtestqt = True 16 | except ImportError: 17 | have_pqtestqt = False 18 | 19 | 20 | def pytest_ignore_collect(collection_path: pathlib.Path, 21 | config: pytest.Config) -> bool | None: 22 | 23 | """Ignore GUI tests if DISPLAY is not set or PyQt is not available.""" 24 | 25 | xdisp = bool(os.getenv("DISPLAY")) 26 | 27 | # Return True to prevent considering this path for collection. 28 | if all((xdisp, have_pyqt6, have_pqtestqt)): 29 | return False 30 | 31 | return True 32 | -------------------------------------------------------------------------------- /tests/gui/widgets/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SELinuxProject/setools/7503c647eecaead2fec30c938eadba0f4a01a69b/tests/gui/widgets/__init__.py -------------------------------------------------------------------------------- /tests/gui/widgets/criteria/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SELinuxProject/setools/7503c647eecaead2fec30c938eadba0f4a01a69b/tests/gui/widgets/criteria/__init__.py -------------------------------------------------------------------------------- /tests/gui/widgets/criteria/test_boolean.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-2.0-only 2 | from PyQt6 import QtCore 3 | import pytest 4 | from pytestqt.qtbot import QtBot 5 | 6 | from setoolsgui.widgets.criteria.boolean import (BooleanList, BooleanName) 7 | from setoolsgui.widgets.models.table import SEToolsTableModel 8 | 9 | 10 | @pytest.fixture 11 | def list_widget(mock_query, request: pytest.FixtureRequest, 12 | qtbot: QtBot) -> BooleanList: 13 | """Pytest fixture to set up the Boolean list widget.""" 14 | marker = request.node.get_closest_marker("obj_args") 15 | kwargs = marker.kwargs if marker else {} 16 | w = BooleanList(request.node.name, mock_query, "name", **kwargs) 17 | qtbot.addWidget(w) 18 | w.show() 19 | return w 20 | 21 | 22 | @pytest.fixture 23 | def name_widget(mock_query, request: pytest.FixtureRequest, 24 | qtbot: QtBot) -> BooleanName: 25 | """Pytest fixture to set up the Boolean name widget.""" 26 | marker = request.node.get_closest_marker("obj_args") 27 | kwargs = marker.kwargs if marker else {} 28 | w = BooleanName(request.node.name, mock_query, "name", **kwargs) 29 | qtbot.addWidget(w) 30 | w.show() 31 | return w 32 | 33 | 34 | def test_bool_list(list_widget: BooleanList, mock_query) -> None: 35 | """Test Boolean name list widget.""" 36 | model = list_widget.criteria.model() 37 | assert isinstance(model, SEToolsTableModel) 38 | assert sorted(mock_query.policy.bools()) == model.item_list 39 | 40 | 41 | def test_bool_name(name_widget: BooleanName, mock_query) -> None: 42 | """Test Boolean name line edit widget.""" 43 | model = name_widget.criteria.completer().model() 44 | assert isinstance(model, QtCore.QStringListModel) 45 | assert sorted(b.name for b in mock_query.policy.bools()) == model.stringList() 46 | -------------------------------------------------------------------------------- /tests/gui/widgets/criteria/test_common.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-2.0-only 2 | from PyQt6 import QtCore 3 | import pytest 4 | from pytestqt.qtbot import QtBot 5 | 6 | from setoolsgui.widgets import criteria 7 | 8 | 9 | @pytest.fixture 10 | def widget(mock_query, request: pytest.FixtureRequest, qtbot: QtBot) -> criteria.CommonName: 11 | """Pytest fixture to set up the widget.""" 12 | marker = request.node.get_closest_marker("obj_args") 13 | kwargs = marker.kwargs if marker else {} 14 | w = criteria.CommonName(request.node.name, mock_query, "name", **kwargs) 15 | qtbot.addWidget(w) 16 | w.show() 17 | return w 18 | 19 | 20 | def test_base_settings(widget: criteria.CommonName, mock_query) -> None: 21 | """Test base properties of widget.""" 22 | model = widget.criteria.completer().model() 23 | assert isinstance(model, QtCore.QStringListModel) 24 | assert sorted(c.name for c in mock_query.policy.commons()) == model.stringList() 25 | -------------------------------------------------------------------------------- /tests/gui/widgets/criteria/test_objclass.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-2.0-only 2 | from typing import cast 3 | 4 | from PyQt6 import QtCore 5 | import pytest 6 | from pytestqt.qtbot import QtBot 7 | 8 | from setoolsgui.widgets.criteria.objclass import ObjClassList, ObjClassName 9 | from setoolsgui.widgets.models import ObjClassTable 10 | 11 | 12 | @pytest.fixture 13 | def list_widget(mock_query, request: pytest.FixtureRequest, qtbot: QtBot) -> ObjClassList: 14 | """Pytest fixture to set up the widget.""" 15 | marker = request.node.get_closest_marker("obj_args") 16 | kwargs = marker.kwargs if marker else {} 17 | w = ObjClassList(request.node.name, mock_query, "name", **kwargs) 18 | qtbot.addWidget(w) 19 | w.show() 20 | return w 21 | 22 | 23 | @pytest.fixture 24 | def name_widget(mock_query, request: pytest.FixtureRequest, qtbot: QtBot) -> ObjClassName: 25 | """Pytest fixture to set up the widget.""" 26 | marker = request.node.get_closest_marker("obj_args") 27 | kwargs = marker.kwargs if marker else {} 28 | w = ObjClassName(request.node.name, mock_query, "name", **kwargs) 29 | qtbot.addWidget(w) 30 | w.show() 31 | return w 32 | 33 | 34 | def test_list_base_settings(list_widget: ObjClassList, mock_query) -> None: 35 | """Test base properties of list widget.""" 36 | model = cast(ObjClassTable, list_widget.criteria.model()) 37 | assert model.item_list == sorted(mock_query.policy.classes()) 38 | 39 | 40 | def test_name_base_settings(name_widget: ObjClassName, mock_query) -> None: 41 | """Test base properties of name widget.""" 42 | model = name_widget.criteria.completer().model() 43 | assert isinstance(model, QtCore.QStringListModel) 44 | assert sorted(r.name for r in mock_query.policy.classes()) == model.stringList() 45 | -------------------------------------------------------------------------------- /tests/gui/widgets/criteria/test_permission.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-2.0-only 2 | import pytest 3 | from pytestqt.qtbot import QtBot 4 | 5 | from setoolsgui.widgets.criteria.permission import PermissionList 6 | 7 | 8 | @pytest.fixture 9 | def widget(mock_query, request: pytest.FixtureRequest, qtbot: QtBot) -> PermissionList: 10 | """Pytest fixture to set up the widget.""" 11 | marker = request.node.get_closest_marker("obj_args") 12 | kwargs = marker.kwargs if marker else {} 13 | w = PermissionList(request.node.name, mock_query, "name", **kwargs) 14 | qtbot.addWidget(w) 15 | w.show() 16 | return w 17 | 18 | 19 | def test_base_settings(widget: PermissionList) -> None: 20 | """Test base properties of widget.""" 21 | assert widget.perm_model.item_list == ["bar_perm1", "bar_perm2", "common_perm", "foo_perm1", 22 | "foo_perm2"] 23 | 24 | 25 | def test_set_classes(widget: PermissionList, mock_query) -> None: 26 | """Test list contents based on class filtering.""" 27 | widget.set_classes([mock_query.policy.classes()[0]]) 28 | assert widget.perm_model.item_list == ["common_perm", "foo_perm1", "foo_perm2"] 29 | 30 | widget.set_classes(list(mock_query.policy.classes())) 31 | assert widget.perm_model.item_list == ["common_perm"] 32 | -------------------------------------------------------------------------------- /tests/gui/widgets/criteria/test_rbacruletype.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-2.0-only 2 | import pytest 3 | from pytestqt.qtbot import QtBot 4 | 5 | from setools import RBACRuletype 6 | 7 | from setoolsgui.widgets.criteria.rbacruletype import RBACRuleType 8 | 9 | 10 | @pytest.fixture 11 | def widget(mock_query, request: pytest.FixtureRequest, qtbot: QtBot) -> RBACRuleType: 12 | """Pytest fixture to set up the widget.""" 13 | marker = request.node.get_closest_marker("obj_args") 14 | kwargs = marker.kwargs if marker else {} 15 | w = RBACRuleType(request.node.name, mock_query, "checkboxes", **kwargs) 16 | qtbot.addWidget(w) 17 | w.show() 18 | return w 19 | 20 | 21 | def test_base_settings(widget: RBACRuleType) -> None: 22 | """Test base properties of widget.""" 23 | assert len(widget.criteria) == len(RBACRuletype) 24 | -------------------------------------------------------------------------------- /tests/gui/widgets/criteria/test_role.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-2.0-only 2 | 3 | from PyQt6 import QtCore 4 | import pytest 5 | from pytestqt.qtbot import QtBot 6 | 7 | from setoolsgui.widgets.criteria.role import RoleName 8 | 9 | 10 | @pytest.fixture 11 | def widget(mock_query, request: pytest.FixtureRequest, qtbot: QtBot) -> RoleName: 12 | """Pytest fixture to set up the widget.""" 13 | marker = request.node.get_closest_marker("obj_args") 14 | kwargs = marker.kwargs if marker else {} 15 | w = RoleName(request.node.name, mock_query, "name", **kwargs) 16 | qtbot.addWidget(w) 17 | w.show() 18 | return w 19 | 20 | 21 | def test_base_settings(widget: RoleName, mock_query) -> None: 22 | """Test base properties of RoleNameWidget.""" 23 | model = widget.criteria.completer().model() 24 | assert isinstance(model, QtCore.QStringListModel) 25 | assert sorted(r.name for r in mock_query.policy.roles()) == model.stringList() 26 | -------------------------------------------------------------------------------- /tests/gui/widgets/criteria/test_teruletype.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-2.0-only 2 | import pytest 3 | from pytestqt.qtbot import QtBot 4 | 5 | from setools import TERuletype 6 | 7 | from setoolsgui.widgets.criteria.teruletype import TERuleType 8 | 9 | 10 | @pytest.fixture 11 | def widget(mock_query, request: pytest.FixtureRequest, qtbot: QtBot) -> TERuleType: 12 | """Pytest fixture to set up the widget.""" 13 | marker = request.node.get_closest_marker("obj_args") 14 | kwargs = marker.kwargs if marker else {} 15 | w = TERuleType(request.node.name, mock_query, "checkboxes", **kwargs) 16 | qtbot.addWidget(w) 17 | w.show() 18 | return w 19 | 20 | 21 | def test_base_settings(widget: TERuleType) -> None: 22 | """Test base properties of TERuleTypeCriteriaWidget.""" 23 | assert len(widget.criteria) == len(TERuletype) 24 | assert not widget.criteria["neverallow"].isEnabled() 25 | assert not widget.criteria["neverallowxperm"].isEnabled() 26 | -------------------------------------------------------------------------------- /tests/gui/widgets/criteria/test_user.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-2.0-only 2 | 3 | from PyQt6 import QtCore 4 | import pytest 5 | from pytestqt.qtbot import QtBot 6 | 7 | from setoolsgui.widgets.criteria import UserName 8 | 9 | 10 | @pytest.fixture 11 | def widget(mock_query, request: pytest.FixtureRequest, qtbot: QtBot) -> UserName: 12 | """Pytest fixture to set up the widget.""" 13 | marker = request.node.get_closest_marker("obj_args") 14 | kwargs = marker.kwargs if marker else {} 15 | w = UserName(request.node.name, mock_query, "name", **kwargs) 16 | qtbot.addWidget(w) 17 | w.show() 18 | return w 19 | 20 | 21 | def test_base_settings(widget: UserName, mock_query) -> None: 22 | """Test base properties of UserNameWidget.""" 23 | model = widget.criteria.completer().model() 24 | assert isinstance(model, QtCore.QStringListModel) 25 | assert sorted(r.name for r in mock_query.policy.users()) == model.stringList() 26 | -------------------------------------------------------------------------------- /tests/gui/widgets/test_boolquery.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-2.0-only 2 | import typing 3 | 4 | from PyQt6 import QtWidgets 5 | import pytest 6 | from pytestqt.qtbot import QtBot 7 | 8 | import setools 9 | from setoolsgui.widgets.boolquery import BoolQueryTab 10 | 11 | 12 | @pytest.fixture 13 | def widget(mock_policy, request: pytest.FixtureRequest, qtbot: QtBot) -> BoolQueryTab: 14 | """Pytest fixture to set up the widget.""" 15 | marker = request.node.get_closest_marker("obj_args") 16 | kwargs = marker.kwargs if marker else {} 17 | w = BoolQueryTab(mock_policy, **kwargs) 18 | qtbot.addWidget(w) 19 | w.show() 20 | return w 21 | 22 | 23 | def test_docs(widget: BoolQueryTab) -> None: 24 | """Check that docs are provided for the widget.""" 25 | assert widget.whatsThis() 26 | assert widget.table_results.whatsThis() 27 | assert widget.raw_results.whatsThis() 28 | 29 | for w in widget.criteria: 30 | assert w.toolTip() 31 | assert w.whatsThis() 32 | 33 | results = typing.cast(QtWidgets.QTabWidget, widget.results) 34 | for index in range(results.count()): 35 | assert results.tabWhatsThis(index) 36 | 37 | 38 | def test_layout(widget: BoolQueryTab) -> None: 39 | """Test the layout of the criteria frame.""" 40 | name, state = widget.criteria 41 | 42 | assert widget.criteria_frame_layout.columnCount() == 2 43 | assert widget.criteria_frame_layout.rowCount() == 2 44 | assert widget.criteria_frame_layout.itemAtPosition(0, 0).widget() == name 45 | assert widget.criteria_frame_layout.itemAtPosition(0, 1).widget() == state 46 | assert widget.criteria_frame_layout.itemAtPosition(1, 0).widget() == widget.buttonBox 47 | assert widget.criteria_frame_layout.itemAtPosition(1, 1).widget() == widget.buttonBox 48 | 49 | 50 | def test_criteria_mapping(widget: BoolQueryTab) -> None: 51 | """Test that widgets save to the correct query fields.""" 52 | name, state = widget.criteria 53 | 54 | assert isinstance(widget.query, setools.BoolQuery) 55 | assert name.attrname == "name" 56 | assert state.attrname == "default" 57 | -------------------------------------------------------------------------------- /tests/gui/widgets/test_boundsquery.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-2.0-only 2 | import typing 3 | 4 | from PyQt6 import QtWidgets 5 | import pytest 6 | from pytestqt.qtbot import QtBot 7 | 8 | import setools 9 | from setoolsgui.widgets.boundsquery import BoundsQueryTab 10 | 11 | 12 | @pytest.fixture 13 | def widget(mock_policy, request: pytest.FixtureRequest, qtbot: QtBot) -> BoundsQueryTab: 14 | """Pytest fixture to set up the widget.""" 15 | marker = request.node.get_closest_marker("obj_args") 16 | kwargs = marker.kwargs if marker else {} 17 | w = BoundsQueryTab(mock_policy, **kwargs) 18 | qtbot.addWidget(w) 19 | w.show() 20 | return w 21 | 22 | 23 | def test_docs(widget: BoundsQueryTab) -> None: 24 | """Check that docs are provided for the widget.""" 25 | assert widget.whatsThis() 26 | assert widget.table_results.whatsThis() 27 | assert widget.raw_results.whatsThis() 28 | 29 | for w in widget.criteria: 30 | assert w.toolTip() 31 | assert w.whatsThis() 32 | 33 | results = typing.cast(QtWidgets.QTabWidget, widget.results) 34 | for index in range(results.count()): 35 | assert results.tabWhatsThis(index) 36 | 37 | 38 | def test_layout(widget: BoundsQueryTab) -> None: 39 | """Test the layout of the criteria frame.""" 40 | rt, parent, child = widget.criteria 41 | 42 | assert widget.criteria_frame_layout.columnCount() == 2 43 | assert widget.criteria_frame_layout.rowCount() == 3 44 | assert widget.criteria_frame_layout.itemAtPosition(0, 0).widget() == rt 45 | assert widget.criteria_frame_layout.itemAtPosition(0, 1) is None 46 | assert widget.criteria_frame_layout.itemAtPosition(1, 0).widget() == parent 47 | assert widget.criteria_frame_layout.itemAtPosition(1, 1).widget() == child 48 | assert widget.criteria_frame_layout.itemAtPosition(2, 0).widget() == widget.buttonBox 49 | assert widget.criteria_frame_layout.itemAtPosition(2, 1).widget() == widget.buttonBox 50 | 51 | 52 | def test_criteria_mapping(widget: BoundsQueryTab) -> None: 53 | """Test that widgets save to the correct query fields.""" 54 | rt, parent, child = widget.criteria 55 | 56 | assert isinstance(widget.query, setools.BoundsQuery) 57 | assert rt.attrname == "ruletype" 58 | assert parent.attrname == "parent" 59 | assert child.attrname == "child" 60 | -------------------------------------------------------------------------------- /tests/gui/widgets/test_categoryquery.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-2.0-only 2 | import typing 3 | 4 | from PyQt6 import QtWidgets 5 | import pytest 6 | from pytestqt.qtbot import QtBot 7 | 8 | import setools 9 | from setoolsgui.widgets.categoryquery import CategoryQueryTab 10 | 11 | 12 | @pytest.fixture 13 | def widget(mock_policy, request: pytest.FixtureRequest, qtbot: QtBot) -> CategoryQueryTab: 14 | """Pytest fixture to set up the widget.""" 15 | marker = request.node.get_closest_marker("obj_args") 16 | kwargs = marker.kwargs if marker else {} 17 | w = CategoryQueryTab(mock_policy, **kwargs) 18 | qtbot.addWidget(w) 19 | w.show() 20 | return w 21 | 22 | 23 | def test_docs(widget: CategoryQueryTab) -> None: 24 | """Check that docs are provided for the widget.""" 25 | assert widget.whatsThis() 26 | assert widget.table_results.whatsThis() 27 | assert widget.raw_results.whatsThis() 28 | 29 | for w in widget.criteria: 30 | assert w.toolTip() 31 | assert w.whatsThis() 32 | 33 | results = typing.cast(QtWidgets.QTabWidget, widget.results) 34 | for index in range(results.count()): 35 | assert results.tabWhatsThis(index) 36 | 37 | 38 | def test_layout(widget: CategoryQueryTab) -> None: 39 | """Test the layout of the criteria frame.""" 40 | name, = widget.criteria 41 | 42 | assert widget.criteria_frame_layout.columnCount() == 2 43 | assert widget.criteria_frame_layout.rowCount() == 2 44 | assert widget.criteria_frame_layout.itemAtPosition(0, 0).widget() == name 45 | assert widget.criteria_frame_layout.itemAtPosition(0, 1) is None 46 | assert widget.criteria_frame_layout.itemAtPosition(1, 0).widget() == widget.buttonBox 47 | assert widget.criteria_frame_layout.itemAtPosition(1, 1).widget() == widget.buttonBox 48 | 49 | 50 | def test_criteria_mapping(widget: CategoryQueryTab) -> None: 51 | """Test that widgets save to the correct query fields.""" 52 | name, = widget.criteria 53 | 54 | assert isinstance(widget.query, setools.CategoryQuery) 55 | assert name.attrname == "name" 56 | -------------------------------------------------------------------------------- /tests/gui/widgets/test_commonquery.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-2.0-only 2 | import typing 3 | 4 | from PyQt6 import QtWidgets 5 | import pytest 6 | from pytestqt.qtbot import QtBot 7 | 8 | import setools 9 | from setoolsgui.widgets.commonquery import CommonQueryTab 10 | 11 | 12 | @pytest.fixture 13 | def widget(mock_policy, request: pytest.FixtureRequest, qtbot: QtBot) -> CommonQueryTab: 14 | """Pytest fixture to set up the widget.""" 15 | marker = request.node.get_closest_marker("obj_args") 16 | kwargs = marker.kwargs if marker else {} 17 | w = CommonQueryTab(mock_policy, **kwargs) 18 | qtbot.addWidget(w) 19 | w.show() 20 | return w 21 | 22 | 23 | def test_docs(widget: CommonQueryTab) -> None: 24 | """Check that docs are provided for the widget.""" 25 | assert widget.whatsThis() 26 | assert widget.table_results.whatsThis() 27 | assert widget.raw_results.whatsThis() 28 | 29 | for w in widget.criteria: 30 | assert w.toolTip() 31 | assert w.whatsThis() 32 | 33 | results = typing.cast(QtWidgets.QTabWidget, widget.results) 34 | for index in range(results.count()): 35 | assert results.tabWhatsThis(index) 36 | 37 | 38 | def test_layout(widget: CommonQueryTab) -> None: 39 | """Test the layout of the criteria frame.""" 40 | name, perms = widget.criteria 41 | 42 | assert widget.criteria_frame_layout.columnCount() == 2 43 | assert widget.criteria_frame_layout.rowCount() == 2 44 | assert widget.criteria_frame_layout.itemAtPosition(0, 0).widget() == name 45 | assert widget.criteria_frame_layout.itemAtPosition(0, 1).widget() == perms 46 | assert widget.criteria_frame_layout.itemAtPosition(1, 0).widget() == widget.buttonBox 47 | assert widget.criteria_frame_layout.itemAtPosition(1, 1).widget() == widget.buttonBox 48 | 49 | 50 | def test_criteria_mapping(widget: CommonQueryTab) -> None: 51 | """Test that widgets save to the correct query fields.""" 52 | name, state = widget.criteria 53 | 54 | assert isinstance(widget.query, setools.CommonQuery) 55 | assert name.attrname == "name" 56 | assert state.attrname == "perms" 57 | -------------------------------------------------------------------------------- /tests/gui/widgets/test_defaultquery.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-2.0-only 2 | import typing 3 | 4 | from PyQt6 import QtWidgets 5 | import pytest 6 | from pytestqt.qtbot import QtBot 7 | 8 | import setools 9 | from setoolsgui.widgets import criteria 10 | from setoolsgui.widgets.defaultquery import DefaultQueryTab 11 | 12 | 13 | @pytest.fixture 14 | def widget(mock_policy, request: pytest.FixtureRequest, qtbot: QtBot) -> DefaultQueryTab: 15 | """Pytest fixture to set up the widget.""" 16 | marker = request.node.get_closest_marker("obj_args") 17 | kwargs = marker.kwargs if marker else {} 18 | w = DefaultQueryTab(mock_policy, **kwargs) 19 | qtbot.addWidget(w) 20 | w.show() 21 | return w 22 | 23 | 24 | def test_docs(widget: DefaultQueryTab) -> None: 25 | """Check that docs are provided for the widget.""" 26 | assert widget.whatsThis() 27 | assert widget.table_results.whatsThis() 28 | assert widget.raw_results.whatsThis() 29 | 30 | for w in widget.criteria: 31 | assert w.toolTip() 32 | assert w.whatsThis() 33 | 34 | results = typing.cast(QtWidgets.QTabWidget, widget.results) 35 | for index in range(results.count()): 36 | assert results.tabWhatsThis(index) 37 | 38 | 39 | def test_layout(widget: DefaultQueryTab) -> None: 40 | """Test the layout of the criteria frame.""" 41 | rt, tclass, dfl = widget.criteria 42 | 43 | assert widget.criteria_frame_layout.columnCount() == 2 44 | assert widget.criteria_frame_layout.rowCount() == 3 45 | assert widget.criteria_frame_layout.itemAtPosition(0, 0).widget() == rt 46 | assert widget.criteria_frame_layout.itemAtPosition(0, 1).widget() == tclass 47 | assert widget.criteria_frame_layout.itemAtPosition(1, 0).widget() == dfl 48 | assert widget.criteria_frame_layout.itemAtPosition(1, 1) is None 49 | assert widget.criteria_frame_layout.itemAtPosition(2, 0).widget() == widget.buttonBox 50 | assert widget.criteria_frame_layout.itemAtPosition(2, 1).widget() == widget.buttonBox 51 | 52 | 53 | def test_criteria_mapping(widget: DefaultQueryTab) -> None: 54 | """Test that widgets save to the correct query fields.""" 55 | rt, tclass, dfl = widget.criteria 56 | 57 | assert isinstance(widget.query, setools.DefaultQuery) 58 | assert rt.attrname == "ruletype" 59 | assert tclass.attrname == "tclass" 60 | assert dfl.attrname == "default" 61 | assert isinstance(dfl, criteria.DefaultValues) 62 | assert dfl.range_attrname == "default_range" 63 | -------------------------------------------------------------------------------- /tests/gui/widgets/test_mlsrulequery.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-2.0-only 2 | from typing import cast 3 | 4 | from PyQt6 import QtWidgets 5 | import pytest 6 | from pytestqt.qtbot import QtBot 7 | 8 | from setoolsgui.widgets.mlsrulequery import MLSRuleQueryTab 9 | 10 | 11 | @pytest.fixture 12 | def widget(mock_policy, request: pytest.FixtureRequest, qtbot: QtBot) -> MLSRuleQueryTab: 13 | """Pytest fixture to set up the widget.""" 14 | marker = request.node.get_closest_marker("obj_args") 15 | kwargs = marker.kwargs if marker else {} 16 | w = MLSRuleQueryTab(mock_policy, **kwargs) 17 | qtbot.addWidget(w) 18 | w.show() 19 | return w 20 | 21 | 22 | def test_docs(widget: MLSRuleQueryTab) -> None: 23 | """Check that docs are provided for the widget.""" 24 | assert widget.whatsThis() 25 | assert widget.table_results.whatsThis() 26 | assert widget.raw_results.whatsThis() 27 | 28 | for w in widget.criteria: 29 | assert w.toolTip() 30 | assert w.whatsThis() 31 | 32 | results = cast(QtWidgets.QTabWidget, widget.results) 33 | for index in range(results.count()): 34 | assert results.tabWhatsThis(index) 35 | 36 | 37 | def test_layout(widget: MLSRuleQueryTab) -> None: 38 | """Test the layout of the criteria frame.""" 39 | rt, src, dst, tclass, dflt = widget.criteria 40 | 41 | assert widget.criteria_frame_layout.columnCount() == 2 42 | assert widget.criteria_frame_layout.rowCount() == 4 43 | assert widget.criteria_frame_layout.itemAtPosition(0, 0).widget() == rt 44 | assert widget.criteria_frame_layout.itemAtPosition(0, 1).widget() == rt 45 | assert widget.criteria_frame_layout.itemAtPosition(1, 0).widget() == src 46 | assert widget.criteria_frame_layout.itemAtPosition(1, 1).widget() == dst 47 | assert widget.criteria_frame_layout.itemAtPosition(2, 0).widget() == tclass 48 | assert widget.criteria_frame_layout.itemAtPosition(2, 1).widget() == dflt 49 | assert widget.criteria_frame_layout.itemAtPosition(3, 0).widget() == widget.buttonBox 50 | assert widget.criteria_frame_layout.itemAtPosition(3, 1).widget() == widget.buttonBox 51 | 52 | 53 | def test_criteria_mapping(widget: MLSRuleQueryTab) -> None: 54 | """Test that widgets save to the correct query fields.""" 55 | rt, src, dst, tclass, dflt = widget.criteria 56 | 57 | assert rt.attrname == "ruletype" 58 | assert src.attrname == "source" 59 | assert dst.attrname == "target" 60 | assert tclass.attrname == "tclass" 61 | assert dflt.attrname == "default" 62 | -------------------------------------------------------------------------------- /tests/gui/widgets/test_objclassquery.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-2.0-only 2 | import typing 3 | 4 | from PyQt6 import QtWidgets 5 | import pytest 6 | from pytestqt.qtbot import QtBot 7 | 8 | import setools 9 | from setoolsgui.widgets.objclassquery import ObjClassQueryTab 10 | 11 | 12 | @pytest.fixture 13 | def widget(mock_policy, request: pytest.FixtureRequest, qtbot: QtBot) -> ObjClassQueryTab: 14 | """Pytest fixture to set up the widget.""" 15 | marker = request.node.get_closest_marker("obj_args") 16 | kwargs = marker.kwargs if marker else {} 17 | w = ObjClassQueryTab(mock_policy, **kwargs) 18 | qtbot.addWidget(w) 19 | w.show() 20 | return w 21 | 22 | 23 | def test_docs(widget: ObjClassQueryTab) -> None: 24 | """Check that docs are provided for the widget.""" 25 | assert widget.whatsThis() 26 | assert widget.table_results.whatsThis() 27 | assert widget.raw_results.whatsThis() 28 | 29 | for w in widget.criteria: 30 | assert w.toolTip() 31 | assert w.whatsThis() 32 | 33 | results = typing.cast(QtWidgets.QTabWidget, widget.results) 34 | for index in range(results.count()): 35 | assert results.tabWhatsThis(index) 36 | 37 | 38 | def test_layout(widget: ObjClassQueryTab) -> None: 39 | """Test the layout of the criteria frame.""" 40 | name, perms = widget.criteria 41 | 42 | assert widget.criteria_frame_layout.columnCount() == 2 43 | assert widget.criteria_frame_layout.rowCount() == 2 44 | assert widget.criteria_frame_layout.itemAtPosition(0, 0).widget() == name 45 | assert widget.criteria_frame_layout.itemAtPosition(0, 1).widget() == perms 46 | assert widget.criteria_frame_layout.itemAtPosition(1, 0).widget() == widget.buttonBox 47 | assert widget.criteria_frame_layout.itemAtPosition(1, 1).widget() == widget.buttonBox 48 | 49 | 50 | def test_criteria_mapping(widget: ObjClassQueryTab) -> None: 51 | """Test that widgets save to the correct query fields.""" 52 | name, perms = widget.criteria 53 | 54 | assert isinstance(widget.query, setools.ObjClassQuery) 55 | assert name.attrname == "name" 56 | assert perms.attrname == "perms" 57 | -------------------------------------------------------------------------------- /tests/gui/widgets/test_rbacrulequery.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-2.0-only 2 | from typing import cast 3 | 4 | from PyQt6 import QtWidgets 5 | import pytest 6 | from pytestqt.qtbot import QtBot 7 | 8 | from setoolsgui.widgets.rbacrulequery import RBACRuleQueryTab 9 | 10 | 11 | @pytest.fixture 12 | def widget(mock_policy, request: pytest.FixtureRequest, qtbot: QtBot) -> RBACRuleQueryTab: 13 | """Pytest fixture to set up the widget.""" 14 | marker = request.node.get_closest_marker("obj_args") 15 | kwargs = marker.kwargs if marker else {} 16 | w = RBACRuleQueryTab(mock_policy, **kwargs) 17 | qtbot.addWidget(w) 18 | w.show() 19 | return w 20 | 21 | 22 | def test_docs(widget: RBACRuleQueryTab) -> None: 23 | """Check that docs are provided for the widget.""" 24 | assert widget.whatsThis() 25 | assert widget.table_results.whatsThis() 26 | assert widget.raw_results.whatsThis() 27 | 28 | for w in widget.criteria: 29 | assert w.toolTip() 30 | assert w.whatsThis() 31 | 32 | results = cast(QtWidgets.QTabWidget, widget.results) 33 | for index in range(results.count()): 34 | assert results.tabWhatsThis(index) 35 | 36 | 37 | def test_layout(widget: RBACRuleQueryTab) -> None: 38 | """Test the layout of the criteria frame.""" 39 | rt, src, dst, tclass, dflt = widget.criteria 40 | 41 | assert widget.criteria_frame_layout.columnCount() == 2 42 | assert widget.criteria_frame_layout.rowCount() == 4 43 | assert widget.criteria_frame_layout.itemAtPosition(0, 0).widget() == rt 44 | assert widget.criteria_frame_layout.itemAtPosition(0, 1).widget() == rt 45 | assert widget.criteria_frame_layout.itemAtPosition(1, 0).widget() == src 46 | assert widget.criteria_frame_layout.itemAtPosition(1, 1).widget() == dst 47 | assert widget.criteria_frame_layout.itemAtPosition(2, 0).widget() == tclass 48 | assert widget.criteria_frame_layout.itemAtPosition(2, 1).widget() == dflt 49 | assert widget.criteria_frame_layout.itemAtPosition(3, 0).widget() == widget.buttonBox 50 | assert widget.criteria_frame_layout.itemAtPosition(3, 1).widget() == widget.buttonBox 51 | 52 | 53 | def test_criteria_mapping(widget: RBACRuleQueryTab) -> None: 54 | """Test that widgets save to the correct query fields.""" 55 | rt, src, dst, tclass, dflt = widget.criteria 56 | 57 | assert rt.attrname == "ruletype" 58 | assert src.attrname == "source" 59 | assert dst.attrname == "target" 60 | assert tclass.attrname == "tclass" 61 | assert dflt.attrname == "default" 62 | -------------------------------------------------------------------------------- /tests/gui/widgets/test_rolequery.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-2.0-only 2 | import typing 3 | 4 | from PyQt6 import QtWidgets 5 | import pytest 6 | from pytestqt.qtbot import QtBot 7 | 8 | import setools 9 | from setoolsgui.widgets.rolequery import RoleQueryTab 10 | 11 | 12 | @pytest.fixture 13 | def widget(mock_policy, request: pytest.FixtureRequest, qtbot: QtBot) -> RoleQueryTab: 14 | """Pytest fixture to set up the widget.""" 15 | marker = request.node.get_closest_marker("obj_args") 16 | kwargs = marker.kwargs if marker else {} 17 | w = RoleQueryTab(mock_policy, **kwargs) 18 | qtbot.addWidget(w) 19 | w.show() 20 | return w 21 | 22 | 23 | def test_docs(widget: RoleQueryTab) -> None: 24 | """Check that docs are provided for the widget.""" 25 | assert widget.whatsThis() 26 | assert widget.table_results.whatsThis() 27 | assert widget.raw_results.whatsThis() 28 | 29 | for w in widget.criteria: 30 | assert w.toolTip() 31 | assert w.whatsThis() 32 | 33 | results = typing.cast(QtWidgets.QTabWidget, widget.results) 34 | for index in range(results.count()): 35 | assert results.tabWhatsThis(index) 36 | 37 | 38 | def test_layout(widget: RoleQueryTab) -> None: 39 | """Test the layout of the criteria frame.""" 40 | name, types = widget.criteria 41 | 42 | assert widget.criteria_frame_layout.columnCount() == 2 43 | assert widget.criteria_frame_layout.rowCount() == 2 44 | assert widget.criteria_frame_layout.itemAtPosition(0, 0).widget() == name 45 | assert widget.criteria_frame_layout.itemAtPosition(0, 1).widget() == types 46 | assert widget.criteria_frame_layout.itemAtPosition(1, 0).widget() == widget.buttonBox 47 | assert widget.criteria_frame_layout.itemAtPosition(1, 1).widget() == widget.buttonBox 48 | 49 | 50 | def test_criteria_mapping(widget: RoleQueryTab) -> None: 51 | """Test that widgets save to the correct query fields.""" 52 | name, types = widget.criteria 53 | 54 | assert isinstance(widget.query, setools.RoleQuery) 55 | assert name.attrname == "name" 56 | assert types.attrname == "types" 57 | -------------------------------------------------------------------------------- /tests/gui/widgets/test_sensitivityquery.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-2.0-only 2 | import typing 3 | 4 | from PyQt6 import QtWidgets 5 | import pytest 6 | from pytestqt.qtbot import QtBot 7 | 8 | import setools 9 | from setoolsgui.widgets.sensitivityquery import SensitivityQueryTab 10 | 11 | 12 | @pytest.fixture 13 | def widget(mock_policy, request: pytest.FixtureRequest, qtbot: QtBot) -> SensitivityQueryTab: 14 | """Pytest fixture to set up the widget.""" 15 | marker = request.node.get_closest_marker("obj_args") 16 | kwargs = marker.kwargs if marker else {} 17 | w = SensitivityQueryTab(mock_policy, **kwargs) 18 | qtbot.addWidget(w) 19 | w.show() 20 | return w 21 | 22 | 23 | def test_docs(widget: SensitivityQueryTab) -> None: 24 | """Check that docs are provided for the widget.""" 25 | assert widget.whatsThis() 26 | assert widget.table_results.whatsThis() 27 | assert widget.raw_results.whatsThis() 28 | 29 | for w in widget.criteria: 30 | assert w.toolTip() 31 | assert w.whatsThis() 32 | 33 | results = typing.cast(QtWidgets.QTabWidget, widget.results) 34 | for index in range(results.count()): 35 | assert results.tabWhatsThis(index) 36 | 37 | 38 | def test_layout(widget: SensitivityQueryTab) -> None: 39 | """Test the layout of the criteria frame.""" 40 | name, = widget.criteria 41 | 42 | assert widget.criteria_frame_layout.columnCount() == 2 43 | assert widget.criteria_frame_layout.rowCount() == 2 44 | assert widget.criteria_frame_layout.itemAtPosition(0, 0).widget() == name 45 | assert widget.criteria_frame_layout.itemAtPosition(0, 1) is None 46 | assert widget.criteria_frame_layout.itemAtPosition(1, 0).widget() == widget.buttonBox 47 | assert widget.criteria_frame_layout.itemAtPosition(1, 1).widget() == widget.buttonBox 48 | 49 | 50 | def test_criteria_mapping(widget: SensitivityQueryTab) -> None: 51 | """Test that widgets save to the correct query fields.""" 52 | name, = widget.criteria 53 | 54 | assert isinstance(widget.query, setools.SensitivityQuery) 55 | assert name.attrname == "name" 56 | -------------------------------------------------------------------------------- /tests/gui/widgets/test_typeattrquery.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-2.0-only 2 | import typing 3 | 4 | from PyQt6 import QtWidgets 5 | import pytest 6 | from pytestqt.qtbot import QtBot 7 | 8 | import setools 9 | from setoolsgui.widgets.typeattrquery import TypeAttributeQueryTab 10 | 11 | 12 | @pytest.fixture 13 | def widget(mock_policy, request: pytest.FixtureRequest, qtbot: QtBot) -> TypeAttributeQueryTab: 14 | """Pytest fixture to set up the widget.""" 15 | marker = request.node.get_closest_marker("obj_args") 16 | kwargs = marker.kwargs if marker else {} 17 | w = TypeAttributeQueryTab(mock_policy, **kwargs) 18 | qtbot.addWidget(w) 19 | w.show() 20 | return w 21 | 22 | 23 | def test_docs(widget: TypeAttributeQueryTab) -> None: 24 | """Check that docs are provided for the widget.""" 25 | assert widget.whatsThis() 26 | assert widget.table_results.whatsThis() 27 | assert widget.raw_results.whatsThis() 28 | 29 | for w in widget.criteria: 30 | assert w.toolTip() 31 | assert w.whatsThis() 32 | 33 | results = typing.cast(QtWidgets.QTabWidget, widget.results) 34 | for index in range(results.count()): 35 | assert results.tabWhatsThis(index) 36 | 37 | 38 | def test_layout(widget: TypeAttributeQueryTab) -> None: 39 | """Test the layout of the criteria frame.""" 40 | name, types = widget.criteria 41 | 42 | assert widget.criteria_frame_layout.columnCount() == 2 43 | assert widget.criteria_frame_layout.rowCount() == 2 44 | assert widget.criteria_frame_layout.itemAtPosition(0, 0).widget() == name 45 | assert widget.criteria_frame_layout.itemAtPosition(0, 1).widget() == types 46 | assert widget.criteria_frame_layout.itemAtPosition(1, 0).widget() == widget.buttonBox 47 | assert widget.criteria_frame_layout.itemAtPosition(1, 1).widget() == widget.buttonBox 48 | 49 | 50 | def test_criteria_mapping(widget: TypeAttributeQueryTab) -> None: 51 | """Test that widgets save to the correct query fields.""" 52 | name, types = widget.criteria 53 | 54 | assert isinstance(widget.query, setools.TypeAttributeQuery) 55 | assert name.attrname == "name" 56 | assert types.attrname == "types" 57 | -------------------------------------------------------------------------------- /tests/gui/widgets/test_typequery.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-2.0-only 2 | import typing 3 | 4 | from PyQt6 import QtWidgets 5 | import pytest 6 | from pytestqt.qtbot import QtBot 7 | 8 | import setools 9 | from setoolsgui.widgets.typequery import TypeQueryTab 10 | 11 | 12 | @pytest.fixture 13 | def widget(mock_policy, request: pytest.FixtureRequest, qtbot: QtBot) -> TypeQueryTab: 14 | """Pytest fixture to set up the widget.""" 15 | marker = request.node.get_closest_marker("obj_args") 16 | kwargs = marker.kwargs if marker else {} 17 | w = TypeQueryTab(mock_policy, **kwargs) 18 | qtbot.addWidget(w) 19 | w.show() 20 | return w 21 | 22 | 23 | def test_docs(widget: TypeQueryTab) -> None: 24 | """Check that docs are provided for the widget.""" 25 | assert widget.whatsThis() 26 | assert widget.table_results.whatsThis() 27 | assert widget.raw_results.whatsThis() 28 | 29 | for w in widget.criteria: 30 | assert w.toolTip() 31 | assert w.whatsThis() 32 | 33 | results = typing.cast(QtWidgets.QTabWidget, widget.results) 34 | for index in range(results.count()): 35 | assert results.tabWhatsThis(index) 36 | 37 | 38 | def test_layout(widget: TypeQueryTab) -> None: 39 | """Test the layout of the criteria frame.""" 40 | name, attrs, permissive = widget.criteria 41 | 42 | assert widget.criteria_frame_layout.columnCount() == 2 43 | assert widget.criteria_frame_layout.rowCount() == 3 44 | assert widget.criteria_frame_layout.itemAtPosition(0, 0).widget() == name 45 | assert widget.criteria_frame_layout.itemAtPosition(0, 1).widget() == permissive 46 | assert widget.criteria_frame_layout.itemAtPosition(1, 0).widget() == attrs 47 | assert widget.criteria_frame_layout.itemAtPosition(1, 1) is None 48 | assert widget.criteria_frame_layout.itemAtPosition(2, 0).widget() == widget.buttonBox 49 | assert widget.criteria_frame_layout.itemAtPosition(2, 1).widget() == widget.buttonBox 50 | 51 | 52 | def test_criteria_mapping(widget: TypeQueryTab) -> None: 53 | """Test that widgets save to the correct query fields.""" 54 | name, attrs, permissive = widget.criteria 55 | 56 | assert isinstance(widget.query, setools.TypeQuery) 57 | assert name.attrname == "name" 58 | assert attrs.attrname == "attrs" 59 | assert permissive.attrname == "permissive" 60 | -------------------------------------------------------------------------------- /tests/gui/widgets/test_userquery.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-2.0-only 2 | import typing 3 | 4 | from PyQt6 import QtWidgets 5 | import pytest 6 | from pytestqt.qtbot import QtBot 7 | 8 | import setools 9 | from setoolsgui.widgets.userquery import UserQueryTab 10 | 11 | 12 | @pytest.fixture 13 | def widget(mock_policy, request: pytest.FixtureRequest, qtbot: QtBot) -> UserQueryTab: 14 | """Pytest fixture to set up the widget.""" 15 | marker = request.node.get_closest_marker("obj_args") 16 | kwargs = marker.kwargs if marker else {} 17 | w = UserQueryTab(mock_policy, **kwargs) 18 | qtbot.addWidget(w) 19 | w.show() 20 | return w 21 | 22 | 23 | def test_docs(widget: UserQueryTab) -> None: 24 | """Check that docs are provided for the widget.""" 25 | assert widget.whatsThis() 26 | assert widget.table_results.whatsThis() 27 | assert widget.raw_results.whatsThis() 28 | 29 | for w in widget.criteria: 30 | assert w.toolTip() 31 | assert w.whatsThis() 32 | 33 | results = typing.cast(QtWidgets.QTabWidget, widget.results) 34 | for index in range(results.count()): 35 | assert results.tabWhatsThis(index) 36 | 37 | 38 | def test_layout(widget: UserQueryTab) -> None: 39 | """Test the layout of the criteria frame.""" 40 | name, roles, lvl, rng = widget.criteria 41 | 42 | assert widget.criteria_frame_layout.columnCount() == 2 43 | assert widget.criteria_frame_layout.rowCount() == 3 44 | assert widget.criteria_frame_layout.itemAtPosition(0, 0).widget() == name 45 | assert widget.criteria_frame_layout.itemAtPosition(0, 1).widget() == roles 46 | assert widget.criteria_frame_layout.itemAtPosition(1, 0).widget() == lvl 47 | assert widget.criteria_frame_layout.itemAtPosition(1, 1).widget() == rng 48 | assert widget.criteria_frame_layout.itemAtPosition(2, 0).widget() == widget.buttonBox 49 | assert widget.criteria_frame_layout.itemAtPosition(2, 1).widget() == widget.buttonBox 50 | 51 | 52 | def test_criteria_mapping(widget: UserQueryTab) -> None: 53 | """Test that widgets save to the correct query fields.""" 54 | name, roles, lvl, rng = widget.criteria 55 | 56 | assert isinstance(widget.query, setools.UserQuery) 57 | assert name.attrname == "name" 58 | assert roles.attrname == "roles" 59 | assert lvl.attrname == "level" 60 | assert rng.attrname == "range_" 61 | -------------------------------------------------------------------------------- /tests/library/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SELinuxProject/setools/7503c647eecaead2fec30c938eadba0f4a01a69b/tests/library/__init__.py -------------------------------------------------------------------------------- /tests/library/checker/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SELinuxProject/setools/7503c647eecaead2fec30c938eadba0f4a01a69b/tests/library/checker/__init__.py -------------------------------------------------------------------------------- /tests/library/checker/checker-invalidoption.ini: -------------------------------------------------------------------------------- 1 | [emptyattr] 2 | desc = empty type attribute test 3 | check_type = empty_typeattr 4 | attr = empty_source_attr 5 | invalid_option = value 6 | -------------------------------------------------------------------------------- /tests/library/checker/checker-invalidtype.ini: -------------------------------------------------------------------------------- 1 | [emptyattr] 2 | check_type = invalid_type 3 | attr = empty_source_attr 4 | -------------------------------------------------------------------------------- /tests/library/checker/checker-invalidvalue.ini: -------------------------------------------------------------------------------- 1 | [emptyattr] 2 | check_type = empty_typeattr 3 | attr = INVALID 4 | -------------------------------------------------------------------------------- /tests/library/checker/checker-missingtype.ini: -------------------------------------------------------------------------------- 1 | [emptyattr] 2 | attr = empty_source_attr 3 | -------------------------------------------------------------------------------- /tests/library/checker/checker-valid.ini: -------------------------------------------------------------------------------- 1 | [emptyattr] 2 | desc = empty type attribute test 3 | check_type = empty_typeattr 4 | attr = empty_source_attr 5 | 6 | [roexec] 7 | desc = read only executables test 8 | check_type = ro_execs 9 | exempt_exec_domain = unconfined 10 | exempt_write_domain = domain1 11 | domain2 unconfined 12 | 13 | [assertte] 14 | check_type = assert_te 15 | perms = null 16 | 17 | [rokmod] 18 | desc = read only kernel modules test 19 | check_type = ro_kmods 20 | exempt_load_domain = unconfined 21 | exempt_write_domain = domain1 22 | domain2 unconfined 23 | -------------------------------------------------------------------------------- /tests/library/checker/util.py: -------------------------------------------------------------------------------- 1 | # Copyright 2020, Microsoft Corporation 2 | # 3 | # SPDX-License-Identifier: GPL-2.0-only 4 | # 5 | from setools.checker import util 6 | 7 | 8 | class TestCheckerUtil: 9 | 10 | def test_config_bool_value(self): 11 | """Test config_bool_value""" 12 | assert util.config_bool_value(" TrUe ") 13 | assert (util.config_bool_value(" 1 ")) 14 | assert (util.config_bool_value(" YeS ")) 15 | assert not util.config_bool_value(" FalsE ") 16 | assert not util.config_bool_value(" 0 ") 17 | assert not util.config_bool_value(" No ") 18 | 19 | assert util.config_bool_value(True) 20 | assert not util.config_bool_value(None) 21 | assert not util.config_bool_value(False) 22 | -------------------------------------------------------------------------------- /tests/library/commonquery.conf: -------------------------------------------------------------------------------- 1 | class infoflow 2 | class null 3 | class rw 4 | 5 | sid kernel 6 | sid security 7 | 8 | common test1 9 | { 10 | hi_w 11 | hi_r 12 | super_r 13 | super_w 14 | } 15 | 16 | common test2a 17 | { 18 | send 19 | recv 20 | } 21 | 22 | common test2b 23 | { 24 | sig 25 | } 26 | 27 | common test10a 28 | { 29 | null 30 | } 31 | 32 | common test10b 33 | { 34 | null 35 | ping 36 | } 37 | 38 | common test11a 39 | { 40 | read 41 | write 42 | } 43 | 44 | common test11b 45 | { 46 | read 47 | } 48 | 49 | common test11c 50 | { 51 | write 52 | } 53 | 54 | common test12a 55 | { 56 | signal 57 | sigchld 58 | } 59 | 60 | common test12b 61 | { 62 | sigkill 63 | } 64 | 65 | class infoflow 66 | inherits test1 67 | 68 | class null 69 | inherits test10a 70 | 71 | class rw 72 | inherits test11a 73 | 74 | sensitivity low_s; 75 | sensitivity medium_s alias med; 76 | sensitivity high_s; 77 | 78 | dominance { low_s med high_s } 79 | 80 | category here; 81 | category there; 82 | category elsewhere alias lost; 83 | 84 | #level decl 85 | level low_s:here.there; 86 | level med:here, elsewhere; 87 | level high_s:here.lost; 88 | 89 | #some constraints 90 | mlsconstrain infoflow hi_r ((l1 dom l2) or (t1 == mls_exempt)); 91 | 92 | attribute mls_exempt; 93 | 94 | type system; 95 | role system; 96 | role system types system; 97 | 98 | allow system system:infoflow hi_r; 99 | 100 | #users 101 | user system roles system level med range low_s - high_s:here.lost; 102 | 103 | #normal constraints 104 | constrain infoflow hi_w (u1 == u2); 105 | 106 | #isids 107 | sid kernel system:system:system:medium_s:here 108 | sid security system:system:system:high_s:lost 109 | 110 | #fs_use 111 | fs_use_trans devpts system:object_r:system:low_s; 112 | fs_use_xattr ext3 system:object_r:system:low_s; 113 | fs_use_task pipefs system:object_r:system:low_s; 114 | 115 | #genfscon 116 | genfscon proc / system:object_r:system:med 117 | genfscon proc /sys system:object_r:system:low_s 118 | genfscon selinuxfs / system:object_r:system:high_s:here.there 119 | 120 | portcon tcp 80 system:object_r:system:low_s 121 | 122 | netifcon eth0 system:object_r:system:low_s system:object_r:system:low_s 123 | 124 | nodecon 127.0.0.1 255.255.255.255 system:object_r:system:low_s:here 125 | nodecon ::1 ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff system:object_r:system:low_s:here 126 | 127 | -------------------------------------------------------------------------------- /tests/library/conditionalinfoflow.conf: -------------------------------------------------------------------------------- 1 | class infoflow 2 | 3 | sid kernel 4 | 5 | class infoflow 6 | { 7 | hi_w 8 | hi_r 9 | med_r 10 | med_w 11 | } 12 | 13 | type system; 14 | role system; 15 | role system types system; 16 | 17 | ################################################# 18 | 19 | type src; 20 | type tgt; 21 | type flow_true; 22 | type flow_false; 23 | 24 | type src_remain; 25 | type tgt_remain; 26 | type flow_remain; 27 | 28 | bool condition false; 29 | 30 | allow src_remain flow_remain:infoflow hi_w; 31 | allow tgt_remain flow_remain:infoflow hi_r; 32 | 33 | if (condition) { 34 | allow src flow_true:infoflow hi_w; 35 | allow tgt flow_true:infoflow hi_r; 36 | allow tgt flow_true:infoflow hi_r; 37 | 38 | allow src_remain flow_remain:infoflow med_w; 39 | allow tgt_remain flow_remain:infoflow med_r; 40 | } 41 | else { 42 | allow src flow_false:infoflow hi_w; 43 | allow tgt flow_false:infoflow hi_r; 44 | } 45 | 46 | ################################################# 47 | 48 | #users 49 | user system roles system; 50 | 51 | #isids 52 | sid kernel system:system:system 53 | -------------------------------------------------------------------------------- /tests/library/invalid_perm_maps/bad-class-keyword: -------------------------------------------------------------------------------- 1 | 5 2 | 3 | invalid infoflow 6 4 | low_w w 1 5 | med_w w 5 6 | hi_w w 10 7 | low_r r 1 8 | med_r r 5 9 | hi_r r 10 10 | 11 | class infoflow2 7 12 | low_w w 1 13 | med_w w 5 14 | hi_w w 10 15 | low_r r 1 16 | med_r r 5 17 | hi_r r 10 18 | super b 10 19 | 20 | class infoflow3 1 21 | null n 1 22 | 23 | class file 2 24 | execute r 10 25 | entrypoint r 10 26 | 27 | class process 1 28 | transition w 10 29 | -------------------------------------------------------------------------------- /tests/library/invalid_perm_maps/bad-perm-weight-high: -------------------------------------------------------------------------------- 1 | 5 2 | 3 | class infoflow 6 4 | low_w w 1 5 | med_w w 5 6 | hi_w w 10 7 | low_r r 1 8 | med_r r 5 9 | hi_r r 10 10 | 11 | class infoflow2 7 12 | low_w w 1 13 | med_w w 5 14 | hi_w w 10 15 | low_r r 1 16 | med_r r 5 17 | hi_r r 10 18 | super b 10 19 | 20 | class infoflow3 1 21 | null n 1 22 | 23 | class file 2 24 | execute r 10 25 | entrypoint r 10 26 | 27 | class process 1 28 | transition w 11 29 | -------------------------------------------------------------------------------- /tests/library/invalid_perm_maps/bad-perm-weight-low: -------------------------------------------------------------------------------- 1 | 5 2 | 3 | class infoflow 6 4 | low_w w 1 5 | med_w w 5 6 | hi_w w 10 7 | low_r r 1 8 | med_r r 5 9 | hi_r r 10 10 | 11 | class infoflow2 7 12 | low_w w 1 13 | med_w w 5 14 | hi_w w 10 15 | low_r r 1 16 | med_r r 5 17 | hi_r r 10 18 | super b 10 19 | 20 | class infoflow3 1 21 | null n 1 22 | 23 | class file 2 24 | execute r 10 25 | entrypoint r 10 26 | 27 | class process 1 28 | transition w -1 29 | -------------------------------------------------------------------------------- /tests/library/invalid_perm_maps/bad-permcount: -------------------------------------------------------------------------------- 1 | 5 2 | 3 | class infoflow 6 4 | low_w w 1 5 | med_w w 5 6 | hi_w w 10 7 | low_r r 1 8 | med_r r 5 9 | hi_r r 10 10 | 11 | class infoflow2 7 12 | low_w w 1 13 | med_w w 5 14 | hi_w w 10 15 | low_r r 1 16 | med_r r 5 17 | hi_r r 10 18 | super b 10 19 | 20 | class infoflow3 INVALID 21 | null n 1 22 | 23 | class file 2 24 | execute r 10 25 | entrypoint r 10 26 | 27 | class process 1 28 | transition w 10 29 | -------------------------------------------------------------------------------- /tests/library/invalid_perm_maps/extra-class: -------------------------------------------------------------------------------- 1 | 4 2 | 3 | class infoflow 6 4 | low_w w 1 5 | med_w w 5 6 | hi_w w 10 7 | low_r r 1 8 | med_r r 5 9 | hi_r r 10 10 | 11 | class infoflow2 7 12 | low_w w 1 13 | med_w w 5 14 | hi_w w 10 15 | low_r r 1 16 | med_r r 5 17 | hi_r r 10 18 | super b 10 19 | 20 | class infoflow3 1 21 | null n 1 22 | 23 | class file 2 24 | execute r 10 25 | entrypoint r 10 26 | 27 | class process 1 28 | transition w 10 29 | -------------------------------------------------------------------------------- /tests/library/invalid_perm_maps/extra-perms: -------------------------------------------------------------------------------- 1 | 5 2 | 3 | class infoflow 6 4 | low_w w 1 5 | med_w w 5 6 | hi_w w 10 7 | low_r r 1 8 | med_r r 5 9 | hi_r r 10 10 | 11 | class infoflow2 6 12 | low_w w 1 13 | med_w w 5 14 | hi_w w 10 15 | low_r r 1 16 | med_r r 5 17 | hi_r r 10 18 | super b 10 19 | 20 | class infoflow3 1 21 | null n 1 22 | 23 | class file 2 24 | execute r 10 25 | entrypoint r 10 26 | 27 | class process 1 28 | transition w 10 29 | -------------------------------------------------------------------------------- /tests/library/invalid_perm_maps/invalid-flowdir: -------------------------------------------------------------------------------- 1 | 5 2 | 3 | class infoflow 6 4 | low_w w 1 5 | med_w w 5 6 | hi_w w 10 7 | low_r r 1 8 | med_r r 5 9 | hi_r r 10 10 | 11 | class infoflow2 7 12 | low_w w 1 13 | med_w w 5 14 | hi_w w 10 15 | low_r X 1 16 | med_r r 5 17 | hi_r r 10 18 | super b 10 19 | 20 | class infoflow3 1 21 | null n 1 22 | 23 | class file 2 24 | execute r 10 25 | entrypoint r 10 26 | 27 | class process 1 28 | transition w 10 29 | -------------------------------------------------------------------------------- /tests/library/invalid_perm_maps/invalid-perm-weight: -------------------------------------------------------------------------------- 1 | 5 2 | 3 | class infoflow 6 4 | low_w w 1 5 | med_w w 5 6 | hi_w w 10 7 | low_r r 1 8 | med_r r 5 9 | hi_r r 10 10 | 11 | class infoflow2 7 12 | low_w w 1 13 | med_w w 5 14 | hi_w w 10 15 | low_r r 1 16 | med_r r 5 17 | hi_r r 10 18 | super b 10 19 | 20 | class infoflow3 1 21 | null n 1 22 | 23 | class file 2 24 | execute r 10 25 | entrypoint r 10 26 | 27 | class process 1 28 | transition w INVALID 29 | -------------------------------------------------------------------------------- /tests/library/invalid_perm_maps/negative-classcount: -------------------------------------------------------------------------------- 1 | -1 2 | 3 | class infoflow 6 4 | low_w w 1 5 | med_w w 5 6 | hi_w w 10 7 | low_r r 1 8 | med_r r 5 9 | hi_r r 10 10 | 11 | class infoflow2 7 12 | low_w w 1 13 | med_w w 5 14 | hi_w w 10 15 | low_r r 1 16 | med_r r 5 17 | hi_r r 10 18 | super b 10 19 | 20 | class infoflow3 1 21 | null n 1 22 | 23 | class file 2 24 | execute r 10 25 | entrypoint r 10 26 | 27 | class process 1 28 | transition w 10 29 | -------------------------------------------------------------------------------- /tests/library/invalid_perm_maps/negative-permcount: -------------------------------------------------------------------------------- 1 | 5 2 | 3 | class infoflow 6 4 | low_w w 1 5 | med_w w 5 6 | hi_w w 10 7 | low_r r 1 8 | med_r r 5 9 | hi_r r 10 10 | 11 | class infoflow2 7 12 | low_w w 1 13 | med_w w 5 14 | hi_w w 10 15 | low_r r 1 16 | med_r r 5 17 | hi_r r 10 18 | super b 10 19 | 20 | class infoflow3 -1 21 | null n 1 22 | 23 | class file 2 24 | execute r 10 25 | entrypoint r 10 26 | 27 | class process 1 28 | transition w 10 29 | -------------------------------------------------------------------------------- /tests/library/invalid_perm_maps/non-number-classcount: -------------------------------------------------------------------------------- 1 | invalid 2 | 3 | class infoflow 6 4 | low_w w 1 5 | med_w w 5 6 | hi_w w 10 7 | low_r r 1 8 | med_r r 5 9 | hi_r r 10 10 | 11 | class infoflow2 7 12 | low_w w 1 13 | med_w w 5 14 | hi_w w 10 15 | low_r r 1 16 | med_r r 5 17 | hi_r r 10 18 | super b 10 19 | 20 | class infoflow3 1 21 | null n 1 22 | 23 | class file 2 24 | execute r 10 25 | entrypoint r 10 26 | 27 | class process 1 28 | transition w 10 29 | -------------------------------------------------------------------------------- /tests/library/perm_map: -------------------------------------------------------------------------------- 1 | # this is an early comment 2 | 3 | 5 4 | 5 | # this is a comment before a class 6 | 7 | class infoflow 6 8 | low_w w 1 9 | med_w w 5 10 | hi_w w 10 11 | low_r r 1 12 | med_r r 5 13 | hi_r r 10 14 | 15 | # this a comment between classes 16 | 17 | class infoflow2 7 18 | 19 | # this is a comment before perms 20 | 21 | low_w w 1 22 | med_w w 5 23 | hi_w w 10 24 | 25 | # this is a comment between perms 26 | 27 | low_r r 1 # this is a comment after a perm 28 | med_r r 5 29 | hi_r r 10 30 | super b 10 31 | 32 | class infoflow3 1 33 | null n 1 34 | 35 | class file 2 36 | execute r 10 37 | entrypoint r 10 38 | 39 | class process 1 40 | transition w 10 41 | 42 | # this is an extra comment at the end 43 | -------------------------------------------------------------------------------- /tests/library/policyrep/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SELinuxProject/setools/7503c647eecaead2fec30c938eadba0f4a01a69b/tests/library/policyrep/__init__.py -------------------------------------------------------------------------------- /tests/library/policyrep/common.conf: -------------------------------------------------------------------------------- 1 | class infoflow 2 | class infoflow2 3 | class infoflow3 4 | class infoflow4 5 | class infoflow7 6 | class infoflow8 7 | class infoflow10 8 | 9 | sid kernel 10 | sid security 11 | 12 | common infoflow 13 | { 14 | low_w 15 | low_r 16 | } 17 | 18 | class infoflow 19 | inherits infoflow 20 | 21 | class infoflow2 22 | inherits infoflow 23 | { 24 | super_w 25 | super_r 26 | } 27 | 28 | class infoflow3 29 | { 30 | null 31 | } 32 | 33 | class infoflow4 34 | inherits infoflow 35 | { 36 | super_w 37 | super_r 38 | super_none 39 | super_both 40 | super_unmapped 41 | } 42 | 43 | class infoflow7 44 | inherits infoflow 45 | { 46 | unmapped 47 | } 48 | 49 | class infoflow8 50 | { 51 | super_w 52 | super_r 53 | } 54 | 55 | class infoflow10 56 | { 57 | read 58 | write 59 | } 60 | 61 | sensitivity low_s; 62 | sensitivity medium_s alias med; 63 | sensitivity high_s; 64 | 65 | dominance { low_s med high_s } 66 | 67 | category here; 68 | category there; 69 | category elsewhere alias lost; 70 | 71 | #level decl 72 | level low_s:here.there; 73 | level med:here, elsewhere; 74 | level high_s:here.lost; 75 | 76 | #some constraints 77 | mlsconstrain infoflow low_r ((l1 dom l2) or (t1 == mls_exempt)); 78 | 79 | attribute mls_exempt; 80 | 81 | type system; 82 | role system; 83 | role system types system; 84 | 85 | allow system self:infoflow3 null; 86 | 87 | #users 88 | user system roles system level med range low_s - high_s:here.lost; 89 | 90 | #normal constraints 91 | constrain infoflow low_w (u1 == u2); 92 | 93 | #isids 94 | sid kernel system:system:system:medium_s:here 95 | sid security system:system:system:high_s:lost 96 | 97 | #fs_use 98 | fs_use_trans devpts system:object_r:system:low_s; 99 | fs_use_xattr ext3 system:object_r:system:low_s; 100 | fs_use_task pipefs system:object_r:system:low_s; 101 | 102 | #genfscon 103 | genfscon proc / system:object_r:system:med 104 | genfscon proc /sys system:object_r:system:low_s 105 | genfscon selinuxfs / system:object_r:system:high_s:here.there 106 | 107 | portcon tcp 80 system:object_r:system:low_s 108 | 109 | netifcon eth0 system:object_r:system:low_s system:object_r:system:low_s 110 | 111 | nodecon 127.0.0.1 255.255.255.255 system:object_r:system:low_s:here 112 | nodecon ::1 ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff system:object_r:system:low_s:here 113 | 114 | -------------------------------------------------------------------------------- /tests/library/policyrep/test_common.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015, Tresys Technology, LLC 2 | # 3 | # SPDX-License-Identifier: GPL-2.0-only 4 | # 5 | import pytest 6 | import setools 7 | 8 | 9 | @pytest.mark.obj_args("tests/library/policyrep/common.conf") 10 | class TestCommon: 11 | 12 | def test_string(self, compiled_policy: setools.SELinuxPolicy) -> None: 13 | """Common: string representation""" 14 | com = list(compiled_policy.commons()).pop() 15 | assert "infoflow" == str(com), str(com) 16 | 17 | def test_perms(self, compiled_policy: setools.SELinuxPolicy) -> None: 18 | """Common: permissions""" 19 | com = list(compiled_policy.commons()).pop() 20 | assert set(["low_w", "low_r"]) == com.perms, com.perms 21 | 22 | def test_statement(self, compiled_policy: setools.SELinuxPolicy) -> None: 23 | """Common: statement.""" 24 | com = list(compiled_policy.commons()).pop() 25 | assert com.statement() in ( 26 | "common infoflow\n{\n\tlow_w\n\tlow_r\n}", 27 | "common infoflow\n{\n\tlow_r\n\tlow_w\n}"), \ 28 | com.statement() 29 | 30 | def test_contains(self, compiled_policy: setools.SELinuxPolicy) -> None: 31 | """Common: contains""" 32 | com = list(compiled_policy.commons()).pop() 33 | assert "low_r" in com 34 | assert "med_r" not in com 35 | -------------------------------------------------------------------------------- /tests/library/policyrep/test_default.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016, Tresys Technology, LLC 2 | # 3 | # SPDX-License-Identifier: GPL-2.0-only 4 | # 5 | from collections import defaultdict 6 | 7 | import pytest 8 | import setools 9 | 10 | 11 | @pytest.mark.obj_args("tests/library/policyrep/default.conf") 12 | class TestDefault: 13 | 14 | @pytest.fixture(autouse=True) 15 | def _load_defaults(self, compiled_policy: setools.SELinuxPolicy) -> None: 16 | self.defaults = defaultdict[str, list[setools.Default]](list) 17 | for d in compiled_policy.defaults(): 18 | self.defaults[d.tclass.name].append(d) 19 | 20 | def test_user(self) -> None: 21 | """Default: default_user methods/attributes.""" 22 | d = self.defaults["infoflow"].pop() 23 | assert setools.DefaultRuletype.default_user == d.ruletype 24 | assert "infoflow" == d.tclass 25 | assert setools.DefaultValue.target == d.default 26 | assert "default_user infoflow target;" == str(d) 27 | assert str(d) == d.statement() 28 | 29 | def test_role(self) -> None: 30 | """Default: default_role methods/attributes.""" 31 | d = self.defaults["infoflow2"].pop() 32 | assert setools.DefaultRuletype.default_role, d.ruletype 33 | assert "infoflow2" == d.tclass 34 | assert setools.DefaultValue.source, d.default 35 | assert "default_role infoflow2 source;" == str(d) 36 | assert str(d) == d.statement() 37 | 38 | def test_type(self) -> None: 39 | """Default: default_type methods/attributes.""" 40 | d = self.defaults["infoflow3"].pop() 41 | assert setools.DefaultRuletype.default_type, d.ruletype 42 | assert "infoflow3" == d.tclass 43 | assert setools.DefaultValue.target, d.default 44 | assert "default_type infoflow3 target;" == str(d) 45 | assert str(d) == d.statement() 46 | 47 | def test_range(self) -> None: 48 | """Default: default_range methods/attributes.""" 49 | d = self.defaults["infoflow4"].pop() 50 | assert isinstance(d, setools.DefaultRange) 51 | assert setools.DefaultRuletype.default_range, d.ruletype 52 | assert "infoflow4" == d.tclass 53 | assert setools.DefaultValue.source, d.default 54 | assert setools.DefaultRangeValue.high == d.default_range 55 | assert "default_range infoflow4 source high;" == str(d) 56 | assert str(d) == d.statement() 57 | -------------------------------------------------------------------------------- /tests/library/policyrep/test_initsid.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015, Tresys Technology, LLC 2 | # 3 | # SPDX-License-Identifier: GPL-2.0-only 4 | # 5 | import pytest 6 | import setools 7 | 8 | 9 | @pytest.mark.obj_args("tests/library/policyrep/initsid.conf") 10 | class TestInitialSID: 11 | 12 | def test_string(self, compiled_policy: setools.SELinuxPolicy) -> None: 13 | """InitialSID: basic string rendering.""" 14 | sids = list(compiled_policy.initialsids()) 15 | assert len(sids) == 1 16 | assert "kernel" == str(sids[0]) 17 | 18 | def test_context(self, compiled_policy: setools.SELinuxPolicy) -> None: 19 | """InitialSID: context.""" 20 | sids = list(compiled_policy.initialsids()) 21 | assert len(sids) == 1 22 | assert "system:system:system:s0" == sids[0].context, sids[0].context 23 | 24 | def test_statement(self, compiled_policy: setools.SELinuxPolicy) -> None: 25 | """InitialSID: statement.""" 26 | sids = list(compiled_policy.initialsids()) 27 | assert len(sids) == 1 28 | assert "sid kernel system:system:system:s0" == sids[0].statement(), sids[0].statement() 29 | -------------------------------------------------------------------------------- /tests/library/policyrep/test_objclass.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015, Tresys Technology, LLC 2 | # 3 | # SPDX-License-Identifier: GPL-2.0-only 4 | # 5 | import pytest 6 | import setools 7 | 8 | 9 | @pytest.mark.obj_args("tests/library/policyrep/objclass.conf") 10 | class TestObjClass: 11 | 12 | def test_string(self, compiled_policy: setools.SELinuxPolicy) -> None: 13 | """ObjClass: string representation""" 14 | cls = compiled_policy.lookup_class("infoflow") 15 | assert "infoflow" == str(cls), str(cls) 16 | 17 | def test_perms(self, compiled_policy: setools.SELinuxPolicy) -> None: 18 | """ObjClass: permissions""" 19 | cls = compiled_policy.lookup_class("infoflow8") 20 | assert frozenset(["super_w", "super_r"]) == cls.perms, f"{cls.perms}" 21 | 22 | def test_statement_wo_common_w_unique(self, compiled_policy: setools.SELinuxPolicy) -> None: 23 | """ObjClass: statement, no common.""" 24 | cls = compiled_policy.lookup_class("infoflow8") 25 | assert cls.statement() in ( 26 | "class infoflow8\n{\n\tsuper_w\n\tsuper_r\n}", 27 | "class infoflow8\n{\n\tsuper_r\n\tsuper_w\n}"), \ 28 | cls.statement() 29 | 30 | def test_statement_w_common_w_unique(self, compiled_policy: setools.SELinuxPolicy) -> None: 31 | """ObjClass: statement, with common.""" 32 | cls = compiled_policy.lookup_class("infoflow6") 33 | assert cls.statement() in ( 34 | "class infoflow6\ninherits com_b\n{\n\tperm1\n\tperm2\n}", 35 | "class infoflow6\ninherits com_b\n{\n\tperm2\n\tperm1\n}"), \ 36 | cls.statement() 37 | 38 | def test_statement_w_common_wo_unique(self, compiled_policy: setools.SELinuxPolicy) -> None: 39 | """ObjClass: statement, with common, no class perms.""" 40 | cls = compiled_policy.lookup_class("infoflow5") 41 | assert cls.statement() == "class infoflow5\ninherits com_a\n", cls.statement() 42 | 43 | def test_contains_wo_common(self, compiled_policy: setools.SELinuxPolicy) -> None: 44 | """ObjClass: contains""" 45 | cls = compiled_policy.lookup_class("infoflow10") 46 | assert "read" in cls 47 | assert "execute" not in cls 48 | 49 | def test_contains_w_common(self, compiled_policy: setools.SELinuxPolicy) -> None: 50 | """ObjClass: contains, with common""" 51 | cls = compiled_policy.lookup_class("infoflow4") 52 | assert "super_both" in cls 53 | assert "hi_w" in cls 54 | assert "unmapped" not in cls 55 | -------------------------------------------------------------------------------- /tests/library/policyrep/test_polcap.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015, Tresys Technology, LLC 2 | # 3 | # SPDX-License-Identifier: GPL-2.0-only 4 | # 5 | import pytest 6 | 7 | 8 | @pytest.mark.obj_args("tests/library/policyrep/polcap.conf") 9 | class TestPolicyCapability: 10 | 11 | def test_string(self, compiled_policy): 12 | """PolCap: basic string rendering.""" 13 | caps = list(compiled_policy.polcaps()) 14 | assert len(caps) == 1 15 | assert "open_perms" == str(caps[0]) 16 | 17 | def test_statement(self, compiled_policy): 18 | """PolCap: statement.""" 19 | caps = list(compiled_policy.polcaps()) 20 | assert len(caps) == 1 21 | assert "policycap open_perms;" == caps[0].statement() 22 | -------------------------------------------------------------------------------- /tests/library/policyrep/test_role.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015, Tresys Technology, LLC 2 | # 3 | # SPDX-License-Identifier: GPL-2.0-only 4 | # 5 | import pytest 6 | import setools 7 | 8 | 9 | @pytest.mark.obj_args("tests/library/policyrep/role.conf") 10 | class TestRole: 11 | 12 | def test_string(self, compiled_policy: setools.SELinuxPolicy) -> None: 13 | """Role basic string rendering.""" 14 | role = compiled_policy.lookup_role("role20_r") 15 | assert "role20_r" == str(role), f"{role}" 16 | 17 | def test_statement_type(self, compiled_policy: setools.SELinuxPolicy) -> None: 18 | """Role statement, one type.""" 19 | role = compiled_policy.lookup_role("role20_r") 20 | assert "role role20_r types system;" == role.statement(), role.statement() 21 | 22 | def test_statement_two_types(self, compiled_policy: setools.SELinuxPolicy) -> None: 23 | """Role statement, two types.""" 24 | role = compiled_policy.lookup_role("rolename21") 25 | assert "role rolename21 types { type31a type31b };" == role.statement(), role.statement() 26 | 27 | def test_statement_decl(self, compiled_policy: setools.SELinuxPolicy) -> None: 28 | """Role statement, no types.""" 29 | # This is an unlikely corner case, where a role 30 | # has been declared but has no types. 31 | role = compiled_policy.lookup_role("rolename22") 32 | assert "role rolename22;" == role.statement(), role.statement() 33 | 34 | def test_types(self, compiled_policy: setools.SELinuxPolicy) -> None: 35 | """Role types generator.""" 36 | role = compiled_policy.lookup_role("rolename23") 37 | types = sorted(role.types()) 38 | assert ["type31b", "type31c"] == types, types 39 | 40 | def test_expand(self, compiled_policy: setools.SELinuxPolicy) -> None: 41 | """Role expansion""" 42 | role = compiled_policy.lookup_role("system") 43 | expanded = list(role.expand()) 44 | assert 1 == len(expanded), expanded 45 | assert role == expanded[0], expanded 46 | -------------------------------------------------------------------------------- /tests/library/policyrep/test_typeattr.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015, Tresys Technology, LLC 2 | # 3 | # SPDX-License-Identifier: GPL-2.0-only 4 | # 5 | import pytest 6 | import setools 7 | 8 | 9 | @pytest.mark.obj_args("tests/library/policyrep/typeattr.conf") 10 | class TestTypeAttribute: 11 | 12 | def test_string(self, compiled_policy: setools.SELinuxPolicy) -> None: 13 | """TypeAttribute basic string rendering.""" 14 | attr = compiled_policy.lookup_typeattr("name10") 15 | assert "name10" == str(attr), f"{attr}" 16 | 17 | def test_attrs(self, compiled_policy: setools.SELinuxPolicy) -> None: 18 | """TypeAttribute attributes""" 19 | attr = compiled_policy.lookup_typeattr("name20") 20 | with pytest.raises(setools.exception.SymbolUseError): 21 | attr.attributes() 22 | 23 | def test_aliases(self, compiled_policy: setools.SELinuxPolicy) -> None: 24 | """TypeAttribute aliases""" 25 | attr = compiled_policy.lookup_typeattr("name30") 26 | with pytest.raises(setools.exception.SymbolUseError): 27 | attr.aliases() 28 | 29 | def test_expand(self, compiled_policy: setools.SELinuxPolicy) -> None: 30 | """TypeAttribute expansion""" 31 | attr = compiled_policy.lookup_typeattr("name40") 32 | assert ['type31a', 'type31b', 'type31c'] == sorted(attr.expand()), sorted(attr.expand()) 33 | 34 | def test_permissive(self, compiled_policy: setools.SELinuxPolicy) -> None: 35 | attr = compiled_policy.lookup_typeattr("name50") 36 | with pytest.raises(setools.exception.SymbolUseError): 37 | attr.ispermissive 38 | 39 | def test_statement(self, compiled_policy: setools.SELinuxPolicy) -> None: 40 | """TypeAttribute basic statement""" 41 | attr = compiled_policy.lookup_typeattr("name60") 42 | assert "attribute name60;" == attr.statement(), attr.statement() 43 | 44 | def test_contains(self, compiled_policy: setools.SELinuxPolicy) -> None: 45 | """TypeAttribute: contains""" 46 | attr = compiled_policy.lookup_typeattr("name70") 47 | assert "type31b" in attr 48 | assert "type30" not in attr 49 | -------------------------------------------------------------------------------- /tests/library/policyrep/user_standard.conf: -------------------------------------------------------------------------------- 1 | class infoflow 2 | class infoflow2 3 | class infoflow3 4 | class infoflow4 5 | class infoflow5 6 | class infoflow6 7 | class infoflow7 8 | 9 | sid kernel 10 | sid security 11 | 12 | common infoflow 13 | { 14 | low_w 15 | med_w 16 | hi_w 17 | low_r 18 | med_r 19 | hi_r 20 | } 21 | 22 | class infoflow 23 | inherits infoflow 24 | 25 | class infoflow2 26 | inherits infoflow 27 | { 28 | super_w 29 | super_r 30 | } 31 | 32 | class infoflow3 33 | { 34 | null 35 | } 36 | 37 | class infoflow4 38 | inherits infoflow 39 | 40 | class infoflow5 41 | inherits infoflow 42 | 43 | class infoflow6 44 | inherits infoflow 45 | 46 | class infoflow7 47 | inherits infoflow 48 | { 49 | super_w 50 | super_r 51 | super_none 52 | super_both 53 | super_unmapped 54 | } 55 | 56 | attribute mls_exempt; 57 | 58 | type system; 59 | role system; 60 | role system types system; 61 | 62 | role role20_r; 63 | role role21a_r; 64 | role role21b_r; 65 | role role21c_r; 66 | 67 | role role20_r types system; 68 | role role21a_r types system; 69 | role role21b_r types system; 70 | role role21c_r types system; 71 | 72 | type type30; 73 | type type31a; 74 | type type31b; 75 | type type31c; 76 | role system types { type30 type31a type31b type31c }; 77 | 78 | allow system self:infoflow hi_w; 79 | 80 | #users 81 | user system roles { system role20_r role21a_r role21b_r role21c_r }; 82 | user user10 roles system; 83 | user user20 roles { role20_r role21a_r }; 84 | 85 | #normal constraints 86 | constrain infoflow hi_w (u1 == u2); 87 | 88 | #isids 89 | sid kernel system:system:system 90 | sid security system:system:system 91 | 92 | #fs_use 93 | fs_use_trans devpts system:object_r:system; 94 | fs_use_xattr ext3 system:object_r:system; 95 | fs_use_task pipefs system:object_r:system; 96 | 97 | #genfscon 98 | genfscon proc / system:object_r:system 99 | genfscon proc /sys system:object_r:system 100 | genfscon selinuxfs / system:object_r:system 101 | portcon tcp 1 system:system:system 102 | netifcon eth0 system:object_r:system system:object_r:system 103 | nodecon 127.0.0.1 255.255.255.255 system:object_r:system 104 | nodecon ::1 ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff system:object_r:system 105 | 106 | -------------------------------------------------------------------------------- /tests/library/test_boolquery.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014, Tresys Technology, LLC 2 | # 3 | # SPDX-License-Identifier: GPL-2.0-only 4 | # 5 | import pytest 6 | import setools 7 | 8 | 9 | @pytest.mark.obj_args("tests/library/boolquery.conf") 10 | class TestBoolQuery: 11 | 12 | def test_unset(self, compiled_policy: setools.SELinuxPolicy) -> None: 13 | """Boolean query with no criteria.""" 14 | # query with no parameters gets all Booleans. 15 | allbools = sorted(str(b) for b in compiled_policy.bools()) 16 | 17 | q = setools.BoolQuery(compiled_policy) 18 | qbools = sorted(str(b) for b in q.results()) 19 | 20 | assert allbools == qbools 21 | 22 | def test_name_exact(self, compiled_policy: setools.SELinuxPolicy) -> None: 23 | """Boolean query with exact match""" 24 | q = setools.BoolQuery(compiled_policy, name="test1") 25 | 26 | bools = sorted(str(b) for b in q.results()) 27 | assert ["test1"] == bools 28 | 29 | def test_name_regex(self, compiled_policy: setools.SELinuxPolicy) -> None: 30 | """Boolean query with regex match.""" 31 | q = setools.BoolQuery(compiled_policy, name="test2(a|b)", name_regex=True) 32 | 33 | bools = sorted(str(b) for b in q.results()) 34 | assert ["test2a", "test2b"] == bools 35 | 36 | def test_default(self, compiled_policy: setools.SELinuxPolicy) -> None: 37 | """Boolean query with default state match.""" 38 | q = setools.BoolQuery(compiled_policy, default=False) 39 | 40 | bools = sorted(str(b) for b in q.results()) 41 | assert ["test10a", "test10b"] == bools 42 | -------------------------------------------------------------------------------- /tests/library/test_categoryquery.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015, Tresys Technology, LLC 2 | # 3 | # SPDX-License-Identifier: GPL-2.0-only 4 | # 5 | import pytest 6 | import setools 7 | 8 | 9 | @pytest.mark.obj_args("tests/library/categoryquery.conf") 10 | class TestCategoryQuery: 11 | 12 | def test_unset(self, compiled_policy: setools.SELinuxPolicy) -> None: 13 | """MLS category query with no criteria.""" 14 | # query with no parameters gets all categories. 15 | allcats = sorted(str(c) for c in compiled_policy.categories()) 16 | 17 | q = setools.CategoryQuery(compiled_policy) 18 | qcats = sorted(str(c) for c in q.results()) 19 | assert allcats == qcats 20 | 21 | def test_name_exact(self, compiled_policy: setools.SELinuxPolicy) -> None: 22 | """MLS category query with exact name match.""" 23 | q = setools.CategoryQuery(compiled_policy, name="test1") 24 | 25 | cats = sorted(str(c) for c in q.results()) 26 | assert ["test1"] == cats 27 | 28 | def test_name_regex(self, compiled_policy: setools.SELinuxPolicy) -> None: 29 | """MLS category query with regex name match.""" 30 | q = setools.CategoryQuery(compiled_policy, name="test2(a|b)", name_regex=True) 31 | 32 | cats = sorted(str(c) for c in q.results()) 33 | assert ["test2a", "test2b"] == cats 34 | 35 | def test_alias_exact(self, compiled_policy: setools.SELinuxPolicy) -> None: 36 | """MLS category query with exact alias match.""" 37 | q = setools.CategoryQuery(compiled_policy, alias="test10a") 38 | 39 | cats = sorted(str(t) for t in q.results()) 40 | assert ["test10c1"] == cats 41 | 42 | def test_alias_regex(self, compiled_policy: setools.SELinuxPolicy) -> None: 43 | """MLS category query with regex alias match.""" 44 | q = setools.CategoryQuery(compiled_policy, alias="test11(a|b)", alias_regex=True) 45 | 46 | cats = sorted(str(t) for t in q.results()) 47 | assert ["test11c1", "test11c2"] == cats 48 | -------------------------------------------------------------------------------- /tests/library/test_commonquery.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014, Tresys Technology, LLC 2 | # 3 | # SPDX-License-Identifier: GPL-2.0-only 4 | # 5 | import pytest 6 | import setools 7 | 8 | 9 | @pytest.mark.obj_args("tests/library/commonquery.conf") 10 | class TestCommonQuery: 11 | 12 | def test_unset(self, compiled_policy: setools.SELinuxPolicy) -> None: 13 | """Common query with no criteria.""" 14 | # query with no parameters gets all types. 15 | commons = sorted(compiled_policy.commons()) 16 | 17 | q = setools.CommonQuery(compiled_policy) 18 | q_commons = sorted(q.results()) 19 | 20 | assert commons == q_commons 21 | 22 | def test_name_exact(self, compiled_policy: setools.SELinuxPolicy) -> None: 23 | """Common query with exact name match.""" 24 | q = setools.CommonQuery(compiled_policy, name="test1") 25 | 26 | commons = sorted(str(c) for c in q.results()) 27 | assert ["test1"] == commons 28 | 29 | def test_name_regex(self, compiled_policy: setools.SELinuxPolicy) -> None: 30 | """Common query with regex name match.""" 31 | q = setools.CommonQuery(compiled_policy, name="test2(a|b)", name_regex=True) 32 | 33 | commons = sorted(str(c) for c in q.results()) 34 | assert ["test2a", "test2b"] == commons 35 | 36 | def test_perm_indirect_intersect(self, compiled_policy: setools.SELinuxPolicy) -> None: 37 | """Common query with intersect permission name patch.""" 38 | q = setools.CommonQuery(compiled_policy, perms=set(["null"]), perms_equal=False) 39 | 40 | commons = sorted(str(c) for c in q.results()) 41 | assert ["test10a", "test10b"] == commons 42 | 43 | def test_perm_indirect_equal(self, compiled_policy: setools.SELinuxPolicy) -> None: 44 | """Common query with equal permission name patch.""" 45 | q = setools.CommonQuery(compiled_policy, perms=set(["read", "write"]), perms_equal=True) 46 | 47 | commons = sorted(str(c) for c in q.results()) 48 | assert ["test11a"] == commons 49 | 50 | def test_perm_indirect_regex(self, compiled_policy: setools.SELinuxPolicy) -> None: 51 | """Common query with regex permission name patch.""" 52 | q = setools.CommonQuery(compiled_policy, perms="sig.+", perms_regex=True) 53 | 54 | commons = sorted(str(c) for c in q.results()) 55 | assert ["test12a", "test12b"] == commons 56 | -------------------------------------------------------------------------------- /tests/library/test_polcapquery.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014, Tresys Technology, LLC 2 | # 3 | # SPDX-License-Identifier: GPL-2.0-only 4 | # 5 | import pytest 6 | import setools 7 | 8 | 9 | @pytest.mark.obj_args("tests/library/polcapquery.conf") 10 | class TestPolCapQuery: 11 | 12 | def test_unset(self, compiled_policy: setools.SELinuxPolicy) -> None: 13 | """Policy capability query with no criteria""" 14 | # query with no parameters gets all capabilities. 15 | allcaps = sorted(compiled_policy.polcaps()) 16 | 17 | q = setools.PolCapQuery(compiled_policy) 18 | qcaps = sorted(q.results()) 19 | 20 | assert allcaps == qcaps 21 | 22 | def test_name_exact(self, compiled_policy: setools.SELinuxPolicy) -> None: 23 | """Policy capability query with exact match""" 24 | q = setools.PolCapQuery(compiled_policy, name="open_perms", name_regex=False) 25 | 26 | caps = sorted(str(c) for c in q.results()) 27 | assert ["open_perms"] == caps 28 | 29 | def test_name_regex(self, compiled_policy: setools.SELinuxPolicy) -> None: 30 | """Policy capability query with regex match""" 31 | q = setools.PolCapQuery(compiled_policy, name="pe?er", name_regex=True) 32 | 33 | caps = sorted(str(c) for c in q.results()) 34 | assert ["network_peer_controls", "open_perms"] == caps 35 | -------------------------------------------------------------------------------- /tests/library/test_rolequery.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014, Tresys Technology, LLC 2 | # 3 | # SPDX-License-Identifier: GPL-2.0-only 4 | # 5 | import pytest 6 | import setools 7 | 8 | 9 | @pytest.mark.obj_args("tests/library/rolequery.conf") 10 | class TestRoleQuery: 11 | 12 | def test_unset(self, compiled_policy: setools.SELinuxPolicy) -> None: 13 | """Role query with no criteria.""" 14 | # query with no parameters gets all types. 15 | roles = sorted(compiled_policy.roles()) 16 | 17 | q = setools.RoleQuery(compiled_policy) 18 | q_roles = sorted(q.results()) 19 | 20 | assert roles == q_roles 21 | 22 | def test_name_exact(self, compiled_policy: setools.SELinuxPolicy) -> None: 23 | """Role query with exact name match.""" 24 | q = setools.RoleQuery(compiled_policy, name="test1") 25 | 26 | roles = sorted(str(r) for r in q.results()) 27 | assert ["test1"] == roles 28 | 29 | def test_name_regex(self, compiled_policy: setools.SELinuxPolicy) -> None: 30 | """Role query with regex name match.""" 31 | q = setools.RoleQuery(compiled_policy, name="test2(a|b)", name_regex=True) 32 | 33 | roles = sorted(str(r) for r in q.results()) 34 | assert ["test2a", "test2b"] == roles 35 | 36 | def test_type_intersect(self, compiled_policy: setools.SELinuxPolicy) -> None: 37 | """Role query with type set intersection.""" 38 | q = setools.RoleQuery(compiled_policy, types=["test10a", "test10b"]) 39 | 40 | roles = sorted(str(r) for r in q.results()) 41 | assert ["test10r1", "test10r2", "test10r3", 42 | "test10r4", "test10r5", "test10r6"] == roles 43 | 44 | def test_type_equality(self, compiled_policy: setools.SELinuxPolicy) -> None: 45 | """Role query with type set equality.""" 46 | q = setools.RoleQuery(compiled_policy, types=["test11a", "test11b"], types_equal=True) 47 | 48 | roles = sorted(str(r) for r in q.results()) 49 | assert ["test11r2"] == roles 50 | 51 | def test_type_regex(self, compiled_policy: setools.SELinuxPolicy) -> None: 52 | """Role query with type set match.""" 53 | q = setools.RoleQuery(compiled_policy, types="test12(a|b)", types_regex=True) 54 | 55 | roles = sorted(str(r) for r in q.results()) 56 | assert ["test12r1", "test12r2", "test12r3", 57 | "test12r4", "test12r5", "test12r6"] == roles 58 | -------------------------------------------------------------------------------- /tests/library/test_roletypesquery.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025, Christian Göttsche 2 | # 3 | # SPDX-License-Identifier: GPL-2.0-only 4 | # 5 | import pytest 6 | import setools 7 | 8 | 9 | @pytest.mark.obj_args("tests/library/roletypesquery.conf") 10 | class TestRoleTypesQuery: 11 | 12 | def test_name_nomatch(self, compiled_policy: setools.SELinuxPolicy) -> None: 13 | """Type with no associated role.""" 14 | q = setools.RoleTypesQuery(compiled_policy, name="test1") 15 | 16 | roles = sorted(str(r) for r in q.results()) 17 | assert [] == roles 18 | 19 | def test_name_onematch(self, compiled_policy: setools.SELinuxPolicy) -> None: 20 | """Type with one associated role.""" 21 | q = setools.RoleTypesQuery(compiled_policy, name="test2a") 22 | 23 | roles = sorted(str(r) for r in q.results()) 24 | assert ["test2ra"] == roles 25 | 26 | def test_name_multiplematches(self, compiled_policy: setools.SELinuxPolicy) -> None: 27 | """Type with multiple associated roles.""" 28 | q = setools.RoleTypesQuery(compiled_policy, name="test3a") 29 | 30 | roles = sorted(str(r) for r in q.results()) 31 | assert ["test3rb", "test3rc", "test3rd"] == roles 32 | 33 | def test_name_multiplematches_regex(self, compiled_policy: setools.SELinuxPolicy) -> None: 34 | """Multiple types with multiple associated roles.""" 35 | q = setools.RoleTypesQuery(compiled_policy, name="test3", name_regex=True) 36 | 37 | roles = sorted(str(r) for r in q.results()) 38 | assert ["test3ra", "test3rb", "test3rc", "test3rd"] == roles 39 | -------------------------------------------------------------------------------- /tests/library/test_typeattrquery.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014-2015, Tresys Technology, LLC 2 | # 3 | # SPDX-License-Identifier: GPL-2.0-only 4 | # 5 | import pytest 6 | import setools 7 | 8 | 9 | @pytest.mark.obj_args("tests/library/typeattrquery.conf") 10 | class TestTypeAttributeQuery: 11 | 12 | def test_unset(self, compiled_policy: setools.SELinuxPolicy) -> None: 13 | """Type attribute query with no criteria.""" 14 | # query with no parameters gets all attrs. 15 | allattrs = sorted(compiled_policy.typeattributes()) 16 | 17 | q = setools.TypeAttributeQuery(compiled_policy) 18 | qattrs = sorted(q.results()) 19 | 20 | assert allattrs == qattrs 21 | 22 | def test_name_exact(self, compiled_policy: setools.SELinuxPolicy) -> None: 23 | """Type attribute query with exact name match.""" 24 | q = setools.TypeAttributeQuery(compiled_policy, name="test1") 25 | 26 | attrs = sorted(str(t) for t in q.results()) 27 | assert ["test1"] == attrs 28 | 29 | def test_name_regex(self, compiled_policy: setools.SELinuxPolicy) -> None: 30 | """Type attribute query with regex name match.""" 31 | q = setools.TypeAttributeQuery(compiled_policy, name="test2(a|b)", name_regex=True) 32 | 33 | attrs = sorted(str(t) for t in q.results()) 34 | assert ["test2a", "test2b"] == attrs 35 | 36 | def test_type_set_intersect(self, compiled_policy: setools.SELinuxPolicy) -> None: 37 | """Type attribute query with type set intersection.""" 38 | q = setools.TypeAttributeQuery(compiled_policy, types=["test10t1", "test10t7"]) 39 | 40 | attrs = sorted(str(t) for t in q.results()) 41 | assert ["test10a", "test10c"] == attrs 42 | 43 | def test_type_set_equality(self, compiled_policy: setools.SELinuxPolicy) -> None: 44 | """Type attribute query with type set equality.""" 45 | q = setools.TypeAttributeQuery(compiled_policy, types=["test11t1", "test11t2", 46 | "test11t3", "test11t5"], types_equal=True) 47 | 48 | attrs = sorted(str(t) for t in q.results()) 49 | assert ["test11a"] == attrs 50 | 51 | def test_type_set_regex(self, compiled_policy: setools.SELinuxPolicy) -> None: 52 | """Type attribute query with type set regex match.""" 53 | q = setools.TypeAttributeQuery(compiled_policy, types="test12t(1|2)", types_regex=True) 54 | 55 | attrs = sorted(str(t) for t in q.results()) 56 | assert ["test12a", "test12b"] == attrs 57 | -------------------------------------------------------------------------------- /tests/library/util.py: -------------------------------------------------------------------------------- 1 | """Unit test mixin classes.""" 2 | # Copyright 2015, Tresys Technology, LLC 3 | # 4 | # SPDX-License-Identifier: GPL-2.0-only 5 | # 6 | # pylint: disable=too-few-public-methods 7 | import pytest 8 | import setools 9 | 10 | 11 | def validate_rule(rule: setools.policyrep.PolicyRule, 12 | ruletype: setools.policyrep.PolicyEnum | str, 13 | source: setools.policyrep.PolicySymbol | str, 14 | target: setools.policyrep.PolicySymbol | str, 15 | /, *, 16 | tclass: setools.ObjClass | str | None = None, 17 | perms: set[str] | setools.XpermSet | None = None, 18 | default: setools.policyrep.PolicySymbol | str | None = None, 19 | cond: str | None = None, 20 | cond_block: bool | None = None, 21 | xperm: str | None = None) -> None: 22 | 23 | """Validate a rule.""" 24 | assert ruletype == rule.ruletype 25 | assert source == rule.source 26 | assert target == rule.target 27 | 28 | if tclass is not None: 29 | assert tclass == rule.tclass 30 | 31 | if perms is not None: 32 | assert perms == rule.perms 33 | 34 | elif default is not None: 35 | assert default == rule.default 36 | 37 | if cond: 38 | assert cond == rule.conditional 39 | assert cond_block == rule.conditional_block 40 | else: 41 | with pytest.raises(setools.exception.RuleNotConditional): 42 | rule.conditional 43 | with pytest.raises(setools.exception.RuleNotConditional): 44 | rule.conditional_block 45 | 46 | if xperm: 47 | assert xperm == rule.xperm_type 48 | assert rule.extended 49 | else: 50 | assert not rule.extended 51 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | minversion = 2.4 3 | envlist = python3, pep8, lint, mypy 4 | 5 | [pycodestyle] 6 | max-line-length = 100 7 | 8 | [testenv:pep8] 9 | deps = {[testenv]deps} 10 | pycodestyle 11 | commands_pre = pycodestyle --version 12 | commands = pycodestyle setools/ setoolsgui/ tests/ seinfo seinfoflow sedta sesearch sediff sechecker apol --statistics 13 | 14 | [testenv:coverage] 15 | #setenv = SETOOLS_COVERAGE = 1 16 | passenv = {[testenv]passenv} 17 | deps = {[testenv]deps} 18 | coverage>=6.0 19 | extras = toml 20 | commands_pre = coverage --version 21 | coverage erase 22 | {[testenv]commands_pre} 23 | commands = coverage run --source=setools,setoolsgui -m pytest tests 24 | coverage report 25 | 26 | [testenv:lint] 27 | passenv = {[testenv]passenv} 28 | deps = {[testenv]deps} 29 | pylint>=2.8.0 30 | commands_pre = pylint --version 31 | {[testenv]commands_pre} 32 | commands = pylint -E setools setoolsgui tests seinfo seinfoflow sedta sesearch sediff sechecker apol 33 | 34 | [testenv:mypy] 35 | deps = {[testenv]deps} 36 | types-setuptools 37 | mypy>=1.6.0 38 | commands_pre = mypy --version 39 | commands = mypy -p setools -p setoolsgui -p tests 40 | mypy --scripts-are-modules seinfo seinfoflow sedta sesearch sediff sechecker apol 41 | 42 | [testenv:install] 43 | deps = {[testenv]deps} 44 | allowlist_externals = find 45 | commands = {envpython} -m pip install --use-pep517 --root {envtmpdir}/setools . 46 | find {envtmpdir}/setools 47 | 48 | [testenv] 49 | passenv = USERSPACE_SRC 50 | DISPLAY 51 | XAUTHORITY 52 | deps = networkx>=2.6 53 | cython>=0.29.14 54 | pytest>=6.0 55 | PyQt6>=6.0 56 | pygraphviz 57 | pytest-qt 58 | pytest-xvfb 59 | commands_pre = {envpython} setup.py build_ext -i 60 | commands = {envpython} -m pytest tests 61 | --------------------------------------------------------------------------------