added support for list of domains

Initial support to filter connections using lists of domains.

The lists must be in hosts format:
- 0.0.0.0 www.domain.com
- 127.0.0.1 www.domain.com

From the rules editor, create a new rule, and select
[x] To this lists of domains

Select a directory with files in hosts format, select [x] Priority rule,
select [x] Deny and click on Apply.

An example of a list in hosts format:
https://www.github.developerdan.com/hosts/lists/ads-and-tracking-extended.txt

Note: you can also add a list of domains to allow, not only domains to
block.

TODOs:
- support for URLs besides directories (local lists).
- support for scheduled updates of the above URLs.

related #298
This commit is contained in:
Gustavo Iñiguez Goia 2021-02-25 13:38:48 +01:00
parent fab5d97cd8
commit 26671ded24
6 changed files with 325 additions and 195 deletions

View file

@ -93,7 +93,9 @@ func (l *Loader) Load(path string) error {
r.Operator.Compile()
if r.Operator.Type == List {
for i := 0; i < len(r.Operator.List); i++ {
r.Operator.List[i].Compile()
if err := r.Operator.List[i].Compile(); err != nil {
log.Warning("Operator.Compile() error: %s: ", err)
}
}
}
diskRules[r.Name] = r.Name
@ -211,14 +213,24 @@ func (l *Loader) replaceUserRule(rule *Rule) (err error) {
l.rules[rule.Name] = rule
l.sortRules()
l.Unlock()
rule.Operator.isCompiled = false
if err := rule.Operator.Compile(); err != nil {
log.Warning("Operator.Compile() error: %s: ", err, rule.Operator.Data)
}
if rule.Operator.Type == List {
// TODO: use List protobuf object instead of un/marshalling to/from json
if err = json.Unmarshal([]byte(rule.Operator.Data), &rule.Operator.List); err != nil {
return fmt.Errorf("Error loading rule of type list: %s", err)
}
// force re-Compile() changed rule
for i := 0; i < len(rule.Operator.List); i++ {
rule.Operator.List[i].isCompiled = false
rule.Operator.List[i].Compile()
if err := rule.Operator.Compile(); err != nil {
log.Warning("Operator.Compile() error: %s: ", err)
}
}
}

View file

@ -29,6 +29,7 @@ const (
Complex = Type("complex") // for future use
List = Type("list")
Network = Type("network")
Lists = Type("lists")
)
// Available operands
@ -46,6 +47,7 @@ const (
OpDstNetwork = Operand("dest.network")
OpProto = Operand("protocol")
OpList = Operand("list")
OpDomainsLists = Operand("lists.domains")
)
type opCallback func(value interface{}) bool
@ -62,6 +64,7 @@ type Operator struct {
re *regexp.Regexp
netMask *net.IPNet
isCompiled bool
lists map[string]string
}
// NewOperator returns a new operator object
@ -97,6 +100,14 @@ func (o *Operator) Compile() error {
return err
}
o.re = re
} else if o.Type == Lists && o.Operand == OpDomainsLists {
if o.Data == "" {
return fmt.Errorf("Operand lists is empty, nothing to load: %s", o)
}
if err := o.loadLists(); err != nil {
return err
}
o.cb = o.domainsListCmp
} else if o.Type == List {
o.Operand = OpList
} else if o.Type == Network {
@ -148,6 +159,18 @@ func (o *Operator) cmpNetwork(destIP interface{}) bool {
return o.netMask.Contains(destIP.(net.IP))
}
func (o *Operator) domainsListCmp(v interface{}) bool {
dstHost := v.(string)
if dstHost == "" {
return false
}
if _, found := o.lists[dstHost]; found {
log.Debug("%s: %s, %s", log.Red("domain list match"), dstHost, o.lists[dstHost])
return true
}
return false
}
func (o *Operator) listMatch(con interface{}) bool {
res := true
for i := 0; i < len(o.List); i++ {
@ -188,6 +211,8 @@ func (o *Operator) Match(con *conman.Connection) bool {
return o.cb(con.DstIP)
} else if o.Operand == OpList {
return o.listMatch(con)
} else if o.Operand == OpDomainsLists {
return o.cb(con.DstHost)
}
return false

View file

@ -7,7 +7,7 @@ class Config:
HELP_URL = "https://github.com/gustavo-iniguez-goya/opensnitch/wiki/Configurations"
RulesTypes = ("list", "simple", "regexp", "network")
RulesTypes = ("list", "lists", "simple", "regexp", "network")
DEFAULT_DURATION_IDX = 6 # until restart
DEFAULT_TARGET_PROCESS = 0

View file

@ -15,7 +15,7 @@ from config import Config
from nodes import Nodes
from database import Database
from version import version
from utils import Message
from utils import Message, FileDialog
DIALOG_UI_PATH = "%s/../res/ruleseditor.ui" % os.path.dirname(sys.modules[__name__].__file__)
class RulesEditorDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]):
@ -46,6 +46,7 @@ class RulesEditorDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]):
self.buttonBox.button(QtWidgets.QDialogButtonBox.Close).clicked.connect(self._cb_close_clicked)
self.buttonBox.button(QtWidgets.QDialogButtonBox.Apply).clicked.connect(self._cb_apply_clicked)
self.buttonBox.button(QtWidgets.QDialogButtonBox.Help).clicked.connect(self._cb_help_clicked)
self.selectListButton.clicked.connect(self._cb_select_list_button_clicked)
self.protoCheck.toggled.connect(self._cb_proto_check_toggled)
self.procCheck.toggled.connect(self._cb_proc_check_toggled)
self.cmdlineCheck.toggled.connect(self._cb_cmdline_check_toggled)
@ -53,6 +54,7 @@ class RulesEditorDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]):
self.uidCheck.toggled.connect(self._cb_uid_check_toggled)
self.dstIPCheck.toggled.connect(self._cb_dstip_check_toggled)
self.dstHostCheck.toggled.connect(self._cb_dsthost_check_toggled)
self.dstListsCheck.toggled.connect(self._cb_dstlists_check_toggled)
if QtGui.QIcon.hasThemeIcon("emblem-default") == False:
self.actionAllowRadio.setIcon(self.style().standardIcon(getattr(QtWidgets.QStyle, "SP_DialogApplyButton")))
@ -76,6 +78,11 @@ class RulesEditorDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]):
def _cb_help_clicked(self):
QtGui.QDesktopServices.openUrl(QtCore.QUrl(Config.HELP_URL))
def _cb_select_list_button_clicked(self):
dirName = FileDialog.select_dir(self, self.dstListsLine.text())
if dirName != None and dirName != "":
self.dstListsLine.setText(dirName)
def _cb_proto_check_toggled(self, state):
self.protoCombo.setEnabled(state)
@ -97,6 +104,10 @@ class RulesEditorDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]):
def _cb_dsthost_check_toggled(self, state):
self.dstHostLine.setEnabled(state)
def _cb_dstlists_check_toggled(self, state):
self.dstListsLine.setEnabled(state)
self.selectListButton.setEnabled(state)
def _set_status_error(self, msg):
self.statusLabel.setStyleSheet('color: red')
self.statusLabel.setText(msg)
@ -184,6 +195,10 @@ class RulesEditorDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]):
self.dstHostCheck.setChecked(False)
self.dstHostLine.setText("")
self.selectListButton.setEnabled(False)
self.dstListsCheck.setChecked(False)
self.dstListsLine.setText("")
def _load_rule(self, addr=None, rule=None):
self._load_nodes(addr)
@ -255,6 +270,12 @@ class RulesEditorDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]):
self.dstHostLine.setEnabled(True)
self.dstHostLine.setText(operator.data)
if operator.operand == "lists.domains":
self.dstListsCheck.setChecked(True)
self.dstListsCheck.setEnabled(True)
self.dstListsLine.setText(operator.data)
self.selectListButton.setEnabled(True)
def _load_nodes(self, addr=None):
try:
self.nodesCombo.clear()
@ -332,7 +353,7 @@ class RulesEditorDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]):
Ensure that some constraints are met:
- Determine if a field can be a regexp.
- Validate regexp.
- Fields cam not be empty.
- Fields cannot be empty.
- If the user has not provided a rule name, auto assign one.
"""
self.rule = ui_pb2.Rule()
@ -497,11 +518,29 @@ class RulesEditorDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]):
if self._is_valid_regex(self.uidLine.text()) == False:
return False, QC.translate("rules", "User ID regexp error")
if self.dstListsCheck.isChecked():
if self.dstListsLine.text() == "":
return False, QC.translate("rules", "Lists field cannot be empty")
if os.path.isdir(self.dstListsLine.text()) == False:
return False, QC.translate("rules", "Lists field must be a directory")
self.rule.operator.type = "lists"
self.rule.operator.operand = "lists.domains"
rule_data.append(
{
'type': 'lists',
'operand': 'lists.domains',
'data': self.dstListsLine.text(),
'sensitive': self.sensitiveCheck.isChecked()
})
self.rule.operator.data = json.dumps(rule_data)
if len(rule_data) > 1:
self.rule.operator.type = "list"
self.rule.operator.operand = ""
self.rule.operator.data = json.dumps(rule_data)
elif len(rule_data) == 1:
else:
self.rule.operator.operand = rule_data[0]['operand']
self.rule.operator.data = rule_data[0]['data']
if self._is_regex(self.rule.operator.data):

View file

@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>651</width>
<height>526</height>
<height>543</height>
</rect>
</property>
<property name="windowTitle">
@ -96,7 +96,7 @@
<property name="checked">
<bool>false</bool>
</property>
<layout class="QGridLayout" name="gridLayout" columnstretch="0,0,0,0,0,0">
<layout class="QGridLayout" name="gridLayout" columnstretch="0,0,0,0,0,0,0">
<property name="sizeConstraint">
<enum>QLayout::SetDefaultConstraint</enum>
</property>
@ -109,22 +109,15 @@
<property name="verticalSpacing">
<number>12</number>
</property>
<item row="7" column="1">
<widget class="QCheckBox" name="cmdlineCheck">
<item row="11" column="1">
<widget class="QCheckBox" name="dstIPCheck">
<property name="text">
<string>From this command line</string>
<string>To this IP / Network</string>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QCheckBox" name="procCheck">
<property name="text">
<string>From this executable</string>
</property>
</widget>
</item>
<item row="4" column="3">
<spacer name="horizontalSpacer_5">
<item row="10" column="4" colspan="2">
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
@ -136,8 +129,18 @@
</property>
</spacer>
</item>
<item row="3" column="3">
<spacer name="horizontalSpacer_4">
<item row="6" column="4" colspan="3">
<widget class="QLineEdit" name="procLine">
<property name="enabled">
<bool>false</bool>
</property>
<property name="placeholderText">
<string>/path/to/executable, .*/bin/executable[0-9\.]+$, ...</string>
</property>
</widget>
</item>
<item row="8" column="4" colspan="2">
<spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
@ -156,87 +159,6 @@
</property>
</widget>
</item>
<item row="6" column="3" colspan="3">
<widget class="QLineEdit" name="procLine">
<property name="enabled">
<bool>false</bool>
</property>
<property name="placeholderText">
<string>/path/to/executable, .*/bin/executable[0-9\.]+$, ...</string>
</property>
</widget>
</item>
<item row="8" column="3" colspan="2">
<spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="11" column="1">
<widget class="QCheckBox" name="dstIPCheck">
<property name="text">
<string>To this IP / Network</string>
</property>
</widget>
</item>
<item row="10" column="5">
<widget class="QLineEdit" name="uidLine">
<property name="enabled">
<bool>false</bool>
</property>
</widget>
</item>
<item row="4" column="5">
<widget class="QComboBox" name="durationCombo">
<item>
<property name="text">
<string>once</string>
</property>
</item>
<item>
<property name="text">
<string>30s</string>
</property>
</item>
<item>
<property name="text">
<string>5m</string>
</property>
</item>
<item>
<property name="text">
<string>15m</string>
</property>
</item>
<item>
<property name="text">
<string>30m</string>
</property>
</item>
<item>
<property name="text">
<string>1h</string>
</property>
</item>
<item>
<property name="text">
<string>until reboot</string>
</property>
</item>
<item>
<property name="text">
<string>always</string>
</property>
</item>
</widget>
</item>
<item row="8" column="1">
<widget class="QCheckBox" name="dstPortCheck">
<property name="text">
@ -244,36 +166,15 @@
</property>
</widget>
</item>
<item row="10" column="1">
<widget class="QCheckBox" name="uidCheck">
<item row="13" column="1">
<widget class="QCheckBox" name="dstListsCheck">
<property name="text">
<string>From this user ID</string>
<string>To this list of domains</string>
</property>
</widget>
</item>
<item row="12" column="3" colspan="3">
<widget class="QLineEdit" name="dstHostLine">
<property name="enabled">
<bool>false</bool>
</property>
<property name="toolTip">
<string>Commas or spaces are not allowed to specify multiple domains.
Use regular expressions instead:
.*(opensnitch|duckduckgo).com
.*\.google.com
or a single domain:
www.gnu.org - it'll only match www.gnu.org, nor ftp.gnu.org, nor www2.gnu.org, ...
gnu.org - it'll only match gnu.org, nor www.gnu.org, nor ftp.gnu.org, ...</string>
</property>
<property name="placeholderText">
<string>www.domain.org, .*\.domain.org</string>
</property>
</widget>
</item>
<item row="10" column="3" colspan="2">
<spacer name="horizontalSpacer_2">
<item row="4" column="4">
<spacer name="horizontalSpacer_5">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
@ -285,53 +186,7 @@ gnu.org - it'll only match gnu.org, nor www.gnu.org, nor ftp.gnu.org, ..
</property>
</spacer>
</item>
<item row="5" column="5">
<widget class="QComboBox" name="protoCombo">
<property name="enabled">
<bool>false</bool>
</property>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Only TCP, UDP or UDPLITE are allowed&lt;/p&gt;&lt;p&gt;You can use regexp, i.e.: ^(TCP|UDP)$&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="editable">
<bool>true</bool>
</property>
<property name="currentText">
<string>TCP</string>
</property>
<item>
<property name="text">
<string>TCP</string>
</property>
</item>
<item>
<property name="text">
<string>UDP</string>
</property>
</item>
<item>
<property name="text">
<string>UDPLITE</string>
</property>
</item>
<item>
<property name="text">
<string>TCP6</string>
</property>
</item>
<item>
<property name="text">
<string>UDP6</string>
</property>
</item>
<item>
<property name="text">
<string>UDPLITE6</string>
</property>
</item>
</widget>
</item>
<item row="11" column="3" colspan="3">
<item row="11" column="4" colspan="3">
<widget class="QComboBox" name="dstIPCombo">
<property name="enabled">
<bool>false</bool>
@ -426,17 +281,98 @@ Note: Commas or spaces are not allowed to separate IPs or networks.</string>
</item>
</widget>
</item>
<item row="4" column="1">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Duration</string>
<item row="3" column="4">
<spacer name="horizontalSpacer_4">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="10" column="6">
<widget class="QLineEdit" name="uidLine">
<property name="enabled">
<bool>false</bool>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QCheckBox" name="protoCheck">
<property name="text">
<string>Protocol</string>
<item row="8" column="6">
<widget class="QLineEdit" name="dstPortLine">
<property name="enabled">
<bool>false</bool>
</property>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;You can specify multiple ports using regular expressions:&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;- 53, 80 or 443:&lt;/p&gt;&lt;p&gt;^(53|80|443)$&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;- 53, 443 or 5551, 5552, 5553, etc:&lt;/p&gt;&lt;p&gt;^(53|443|555[0-9])$&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item row="4" column="6">
<widget class="QComboBox" name="durationCombo">
<item>
<property name="text">
<string>once</string>
</property>
</item>
<item>
<property name="text">
<string>30s</string>
</property>
</item>
<item>
<property name="text">
<string>5m</string>
</property>
</item>
<item>
<property name="text">
<string>15m</string>
</property>
</item>
<item>
<property name="text">
<string>30m</string>
</property>
</item>
<item>
<property name="text">
<string>1h</string>
</property>
</item>
<item>
<property name="text">
<string>until reboot</string>
</property>
</item>
<item>
<property name="text">
<string>always</string>
</property>
</item>
</widget>
</item>
<item row="12" column="4" colspan="3">
<widget class="QLineEdit" name="dstHostLine">
<property name="enabled">
<bool>false</bool>
</property>
<property name="toolTip">
<string>Commas or spaces are not allowed to specify multiple domains.
Use regular expressions instead:
.*(opensnitch|duckduckgo).com
.*\.google.com
or a single domain:
www.gnu.org - it'll only match www.gnu.org, nor ftp.gnu.org, nor www2.gnu.org, ...
gnu.org - it'll only match gnu.org, nor www.gnu.org, nor ftp.gnu.org, ...</string>
</property>
<property name="placeholderText">
<string>www.domain.org, .*\.domain.org</string>
</property>
</widget>
</item>
@ -447,14 +383,81 @@ Note: Commas or spaces are not allowed to separate IPs or networks.</string>
</property>
</widget>
</item>
<item row="7" column="3" colspan="3">
<item row="7" column="4" colspan="3">
<widget class="QLineEdit" name="cmdlineLine">
<property name="enabled">
<bool>false</bool>
</property>
</widget>
</item>
<item row="3" column="5">
<item row="4" column="1">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Duration</string>
</property>
</widget>
</item>
<item row="5" column="6">
<widget class="QComboBox" name="protoCombo">
<property name="enabled">
<bool>false</bool>
</property>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Only TCP, UDP or UDPLITE are allowed&lt;/p&gt;&lt;p&gt;You can use regexp, i.e.: ^(TCP|UDP)$&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="editable">
<bool>true</bool>
</property>
<property name="currentText">
<string>TCP</string>
</property>
<item>
<property name="text">
<string>TCP</string>
</property>
</item>
<item>
<property name="text">
<string>UDP</string>
</property>
</item>
<item>
<property name="text">
<string>UDPLITE</string>
</property>
</item>
<item>
<property name="text">
<string>TCP6</string>
</property>
</item>
<item>
<property name="text">
<string>UDP6</string>
</property>
</item>
<item>
<property name="text">
<string>UDPLITE6</string>
</property>
</item>
</widget>
</item>
<item row="5" column="1">
<widget class="QCheckBox" name="protoCheck">
<property name="text">
<string>Protocol</string>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QCheckBox" name="procCheck">
<property name="text">
<string>From this executable</string>
</property>
</widget>
</item>
<item row="3" column="6">
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QRadioButton" name="actionDenyRadio">
@ -498,16 +501,47 @@ Note: Commas or spaces are not allowed to separate IPs or networks.</string>
</item>
</layout>
</item>
<item row="8" column="5">
<widget class="QLineEdit" name="dstPortLine">
<property name="enabled">
<bool>false</bool>
</property>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;You can specify multiple ports using regular expressions:&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;- 53, 80 or 443:&lt;/p&gt;&lt;p&gt;^(53|80|443)$&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;- 53, 443 or 5551, 5552, 5553, etc:&lt;/p&gt;&lt;p&gt;^(53|443|555[0-9])$&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
<item row="7" column="1">
<widget class="QCheckBox" name="cmdlineCheck">
<property name="text">
<string>From this command line</string>
</property>
</widget>
</item>
<item row="10" column="1">
<widget class="QCheckBox" name="uidCheck">
<property name="text">
<string>From this user ID</string>
</property>
</widget>
</item>
<item row="13" column="4" colspan="3">
<layout class="QHBoxLayout" name="horizontalLayout_6">
<item>
<widget class="QPushButton" name="selectListButton">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset theme="search"/>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="dstListsLine">
<property name="enabled">
<bool>false</bool>
</property>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Select a directory with lists of domains to block or allow.&lt;/p&gt;&lt;p&gt;Put inside that directory files with any extension containing lists of domains.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;The format of each entry of a list is as follow (hosts format):&lt;/p&gt;&lt;p&gt;127.0.0.1 www.domain.com&lt;/p&gt;&lt;p&gt;or &lt;/p&gt;&lt;p&gt;0.0.0.0 www.domain.com&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>

View file

@ -6,9 +6,8 @@ class Message():
@staticmethod
def ok(title, message, icon):
msgBox = QtWidgets.QMessageBox()
msgBox.setText(title)
msgBox.setText("<b>{0}</b><br><br>{1}".format(title, message))
msgBox.setIcon(icon)
msgBox.setInformativeText(message)
msgBox.setStandardButtons(QtWidgets.QMessageBox.Ok)
msgBox.exec_()
@ -21,3 +20,24 @@ class Message():
msgBox.setStandardButtons(QtWidgets.QMessageBox.Cancel | QtWidgets.QMessageBox.Yes)
msgBox.setDefaultButton(QtWidgets.QMessageBox.Cancel)
return msgBox.exec_()
class FileDialog():
@staticmethod
def save(parent):
options = QtWidgets.QFileDialog.Options()
fileName, _ = QtWidgets.QFileDialog.getSaveFileName(parent, "", "","All Files (*)", options=options)
return fileName
@staticmethod
def select(parent):
options = QtWidgets.QFileDialog.Options()
fileName, _ = QtWidgets.QFileDialog.getOpenFileName(parent, "", "","All Files (*)", options=options)
return fileName
@staticmethod
def select_dir(parent, current_dir):
options = QtWidgets.QFileDialog.Options()
fileName = QtWidgets.QFileDialog.getExistingDirectory(parent, "", current_dir, options)
return fileName