mirror of
https://gitlab.com/fdroid/fdroidserver.git
synced 2024-11-20 13:50:12 +01:00
add --refresh to scanner
This commit is contained in:
parent
4a38908bd7
commit
bfcc30b854
@ -21,7 +21,6 @@ import re
|
|||||||
import sys
|
import sys
|
||||||
import json
|
import json
|
||||||
import imghdr
|
import imghdr
|
||||||
import shutil
|
|
||||||
import logging
|
import logging
|
||||||
import zipfile
|
import zipfile
|
||||||
import itertools
|
import itertools
|
||||||
@ -147,10 +146,12 @@ class SignatureDataVersionMismatchException(Exception):
|
|||||||
|
|
||||||
|
|
||||||
class SignatureDataController:
|
class SignatureDataController:
|
||||||
def __init__(self, name, filename):
|
def __init__(self, name, filename, url):
|
||||||
self.name = name
|
self.name = name
|
||||||
self.filename = filename
|
self.filename = filename
|
||||||
self.cache_outdated_interval = None
|
self.url = url
|
||||||
|
# by default we assume cache is valid indefinitely
|
||||||
|
self.cache_outdated_interval = timedelta(days=999999)
|
||||||
self.data = {}
|
self.data = {}
|
||||||
|
|
||||||
def check_data_version(self):
|
def check_data_version(self):
|
||||||
@ -165,7 +166,7 @@ class SignatureDataController:
|
|||||||
is not ok.
|
is not ok.
|
||||||
|
|
||||||
:raises SignatureDataMalformedException: when timestamp value is
|
:raises SignatureDataMalformedException: when timestamp value is
|
||||||
inaccessible or not parseable
|
inaccessible or not parse-able
|
||||||
:raises SignatureDataOutdatedException: when timestamp is older then
|
:raises SignatureDataOutdatedException: when timestamp is older then
|
||||||
`self.cache_outdated_interval`
|
`self.cache_outdated_interval`
|
||||||
'''
|
'''
|
||||||
@ -178,9 +179,8 @@ class SignatureDataController:
|
|||||||
raise SignatureDataMalformedException() from e
|
raise SignatureDataMalformedException() from e
|
||||||
except TypeError as e:
|
except TypeError as e:
|
||||||
raise SignatureDataMalformedException() from e
|
raise SignatureDataMalformedException() from e
|
||||||
if self.cache_outdated_interval:
|
if (timestamp + self.cache_outdated_interval) < scanner._datetime_now():
|
||||||
if (timestamp + self.cache_outdated_interval) < scanner._datetime_now():
|
raise SignatureDataOutdatedException()
|
||||||
raise SignatureDataOutdatedException()
|
|
||||||
|
|
||||||
def fetch(self):
|
def fetch(self):
|
||||||
try:
|
try:
|
||||||
@ -233,24 +233,31 @@ class SignatureDataController:
|
|||||||
self.check_data_version()
|
self.check_data_version()
|
||||||
valid_keys = ['timestamp', 'version', 'signatures']
|
valid_keys = ['timestamp', 'version', 'signatures']
|
||||||
|
|
||||||
for k in [x for x in self.data.keys() if x not in valid_keys]:
|
for k in list(self.data.keys()):
|
||||||
del self.data[k]
|
if k not in valid_keys:
|
||||||
|
del self.data[k]
|
||||||
|
|
||||||
|
def fetch_signatures_from_web(self):
|
||||||
|
logging.debug(_("downloading '{}'").format(self.url))
|
||||||
|
with urllib.request.urlopen(self.url) as f:
|
||||||
|
self.data = json.load(f)
|
||||||
|
|
||||||
|
|
||||||
class ExodusSignatureDataController(SignatureDataController):
|
class ExodusSignatureDataController(SignatureDataController):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__('Exodus signatures', 'exodus.yml')
|
super().__init__('Exodus signatures', 'exodus.yml', 'https://reports.exodus-privacy.eu.org/api/trackers')
|
||||||
self.cache_outdated_interval = timedelta(days=1) # refresh exodus cache after one day
|
self.cache_outdated_interval = timedelta(days=1) # refresh exodus cache after one day
|
||||||
|
|
||||||
def fetch_signatures_from_web(self):
|
def fetch_signatures_from_web(self):
|
||||||
exodus_url = "https://reports.exodus-privacy.eu.org/api/trackers"
|
logging.debug(_("downloading '{}'").format(self.url))
|
||||||
|
|
||||||
self.data = {
|
self.data = {
|
||||||
"signatures": {},
|
"signatures": {},
|
||||||
"timestamp": scanner._datetime_now().isoformat(),
|
"timestamp": scanner._datetime_now().isoformat(),
|
||||||
"version": SCANNER_CACHE_VERSION,
|
"version": SCANNER_CACHE_VERSION,
|
||||||
}
|
}
|
||||||
|
|
||||||
with urllib.request.urlopen(exodus_url) as f:
|
with urllib.request.urlopen(self.url) as f:
|
||||||
d = json.load(f)
|
d = json.load(f)
|
||||||
for tracker in d["trackers"].values():
|
for tracker in d["trackers"].values():
|
||||||
if tracker.get('code_signature'):
|
if tracker.get('code_signature'):
|
||||||
@ -261,21 +268,26 @@ class ExodusSignatureDataController(SignatureDataController):
|
|||||||
# "network_signatures": [tracker["network_signature"]],
|
# "network_signatures": [tracker["network_signature"]],
|
||||||
"AntiFeatures": ["Tracking"],
|
"AntiFeatures": ["Tracking"],
|
||||||
"license": "NonFree" # We assume all trackers in exodus
|
"license": "NonFree" # We assume all trackers in exodus
|
||||||
# are non-free, alought free
|
# are non-free, although free
|
||||||
# trackers like piwik, acra,
|
# trackers like piwik, acra,
|
||||||
# etc. might be listed by exodus
|
# etc. might be listed by exodus
|
||||||
# too.
|
# too.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class SUSSDataController(SignatureDataController):
|
|
||||||
def __init__(self):
|
|
||||||
super().__init__('SUSS', 'suss.json')
|
|
||||||
|
|
||||||
|
|
||||||
class ScannerTool():
|
class ScannerTool():
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.sdcs = [SUSSDataController()]
|
self.sdcs = [
|
||||||
|
SignatureDataController(
|
||||||
|
'SUSS',
|
||||||
|
'suss.json',
|
||||||
|
'https://fdroid.gitlab.io/fdroid-suss/suss.json'
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
# we could add support for loading additional signature source
|
||||||
|
# definitions from config.yml here
|
||||||
|
|
||||||
self.load()
|
self.load()
|
||||||
self.compile_regexes()
|
self.compile_regexes()
|
||||||
|
|
||||||
@ -301,11 +313,9 @@ class ScannerTool():
|
|||||||
for sig in sigdef.get('warn_gradle_signatures', []):
|
for sig in sigdef.get('warn_gradle_signatures', []):
|
||||||
self.regexs['warn_gradle_signatures'][sig] = re.compile('.*' + sig, re.IGNORECASE)
|
self.regexs['warn_gradle_signatures'][sig] = re.compile('.*' + sig, re.IGNORECASE)
|
||||||
|
|
||||||
def clear_cache(self):
|
def refresh(self):
|
||||||
# delete cache folder and all its contents
|
for sdc in self.sdcs:
|
||||||
shutil.rmtree(scanner._scanner_cachedir(), ignore_errors=True)
|
sdc.fetch_signatures_from_web()
|
||||||
# re-initialize, this will re-populate the cache from default values
|
|
||||||
self.__init__()
|
|
||||||
|
|
||||||
def add(self, new_controller: SignatureDataController):
|
def add(self, new_controller: SignatureDataController):
|
||||||
self.sdcs.append(new_controller)
|
self.sdcs.append(new_controller)
|
||||||
@ -660,8 +670,8 @@ def main():
|
|||||||
help=_("Force scan of disabled apps and builds."))
|
help=_("Force scan of disabled apps and builds."))
|
||||||
parser.add_argument("--json", action="store_true", default=False,
|
parser.add_argument("--json", action="store_true", default=False,
|
||||||
help=_("Output JSON to stdout."))
|
help=_("Output JSON to stdout."))
|
||||||
parser.add_argument("--clear-cache", action="store_true", default=False,
|
parser.add_argument("--refresh", "-r", action="store_true", default=False,
|
||||||
help=_("purge local scanner definitions cache"))
|
help=_("fetach the latest version of signatures from the web"))
|
||||||
metadata.add_metadata_arguments(parser)
|
metadata.add_metadata_arguments(parser)
|
||||||
options = parser.parse_args()
|
options = parser.parse_args()
|
||||||
metadata.warnings_action = options.W
|
metadata.warnings_action = options.W
|
||||||
@ -676,11 +686,14 @@ def main():
|
|||||||
# initialize/load configuration values
|
# initialize/load configuration values
|
||||||
common.get_config(options)
|
common.get_config(options)
|
||||||
|
|
||||||
if options.clear_cache:
|
if options.refresh:
|
||||||
scanner._get_tool().clear_cache()
|
scanner._get_tool().refresh()
|
||||||
if options.exodus:
|
if options.exodus:
|
||||||
c = ExodusSignatureDataController()
|
c = ExodusSignatureDataController()
|
||||||
c.fetch()
|
if options.refresh:
|
||||||
|
c.fetch_signatures_from_web()
|
||||||
|
else:
|
||||||
|
c.fetch()
|
||||||
scanner._get_tool().add(c)
|
scanner._get_tool().add(c)
|
||||||
|
|
||||||
probcount = 0
|
probcount = 0
|
||||||
|
@ -4,7 +4,6 @@ import glob
|
|||||||
import inspect
|
import inspect
|
||||||
import logging
|
import logging
|
||||||
import optparse
|
import optparse
|
||||||
import io
|
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import shutil
|
import shutil
|
||||||
@ -500,56 +499,56 @@ class Test_scan_binary(unittest.TestCase):
|
|||||||
class Test_SignatureDataController(unittest.TestCase):
|
class Test_SignatureDataController(unittest.TestCase):
|
||||||
# __init__
|
# __init__
|
||||||
def test_init(self):
|
def test_init(self):
|
||||||
sdc = fdroidserver.scanner.SignatureDataController('nnn', 'fff.yml')
|
sdc = fdroidserver.scanner.SignatureDataController('nnn', 'fff.yml', 'https://example.com/test.json')
|
||||||
self.assertEqual(sdc.name, 'nnn')
|
self.assertEqual(sdc.name, 'nnn')
|
||||||
self.assertEqual(sdc.filename, 'fff.yml')
|
self.assertEqual(sdc.filename, 'fff.yml')
|
||||||
self.assertEqual(sdc.cache_outdated_interval, None)
|
self.assertEqual(sdc.cache_outdated_interval, timedelta(999999))
|
||||||
self.assertDictEqual(sdc.data, {})
|
self.assertDictEqual(sdc.data, {})
|
||||||
|
|
||||||
# check_last_updated
|
# check_last_updated
|
||||||
def test_check_last_updated_ok(self):
|
def test_check_last_updated_ok(self):
|
||||||
sdc = fdroidserver.scanner.SignatureDataController('nnn', 'fff.yml')
|
sdc = fdroidserver.scanner.SignatureDataController('nnn', 'fff.yml', 'https://example.com/test.json')
|
||||||
sdc.data['timestamp'] = datetime.now().astimezone().isoformat()
|
sdc.data['timestamp'] = datetime.now().astimezone().isoformat()
|
||||||
sdc.check_last_updated()
|
sdc.check_last_updated()
|
||||||
|
|
||||||
def test_check_last_updated_exception_cache_outdated(self):
|
def test_check_last_updated_exception_cache_outdated(self):
|
||||||
sdc = fdroidserver.scanner.SignatureDataController('nnn', 'fff.yml')
|
sdc = fdroidserver.scanner.SignatureDataController('nnn', 'fff.yml', 'https://example.com/test.json')
|
||||||
sdc.cache_outdated_interval = timedelta(days=7)
|
sdc.cache_outdated_interval = timedelta(days=7)
|
||||||
sdc.data['timestamp'] = (datetime.now().astimezone() - timedelta(days=30)).isoformat()
|
sdc.data['timestamp'] = (datetime.now().astimezone() - timedelta(days=30)).isoformat()
|
||||||
with self.assertRaises(fdroidserver.scanner.SignatureDataOutdatedException):
|
with self.assertRaises(fdroidserver.scanner.SignatureDataOutdatedException):
|
||||||
sdc.check_last_updated()
|
sdc.check_last_updated()
|
||||||
|
|
||||||
def test_check_last_updated_exception_missing_timestamp_value(self):
|
def test_check_last_updated_exception_missing_timestamp_value(self):
|
||||||
sdc = fdroidserver.scanner.SignatureDataController('nnn', 'fff.yml')
|
sdc = fdroidserver.scanner.SignatureDataController('nnn', 'fff.yml', 'https://example.com/test.json')
|
||||||
with self.assertRaises(fdroidserver.scanner.SignatureDataMalformedException):
|
with self.assertRaises(fdroidserver.scanner.SignatureDataMalformedException):
|
||||||
sdc.check_last_updated()
|
sdc.check_last_updated()
|
||||||
|
|
||||||
def test_check_last_updated_exception_not_string(self):
|
def test_check_last_updated_exception_not_string(self):
|
||||||
sdc = fdroidserver.scanner.SignatureDataController('nnn', 'fff.yml')
|
sdc = fdroidserver.scanner.SignatureDataController('nnn', 'fff.yml', 'https://example.com/test.json')
|
||||||
sdc.data['timestamp'] = 12345
|
sdc.data['timestamp'] = 12345
|
||||||
with self.assertRaises(fdroidserver.scanner.SignatureDataMalformedException):
|
with self.assertRaises(fdroidserver.scanner.SignatureDataMalformedException):
|
||||||
sdc.check_last_updated()
|
sdc.check_last_updated()
|
||||||
|
|
||||||
def test_check_last_updated_exception_not_iso_formatted_string(self):
|
def test_check_last_updated_exception_not_iso_formatted_string(self):
|
||||||
sdc = fdroidserver.scanner.SignatureDataController('nnn', 'fff.yml')
|
sdc = fdroidserver.scanner.SignatureDataController('nnn', 'fff.yml', 'https://example.com/test.json')
|
||||||
sdc.data['timestamp'] = '01/09/2002 10:11'
|
sdc.data['timestamp'] = '01/09/2002 10:11'
|
||||||
with self.assertRaises(fdroidserver.scanner.SignatureDataMalformedException):
|
with self.assertRaises(fdroidserver.scanner.SignatureDataMalformedException):
|
||||||
sdc.check_last_updated()
|
sdc.check_last_updated()
|
||||||
|
|
||||||
# check_data_version
|
# check_data_version
|
||||||
def test_check_data_version_ok(self):
|
def test_check_data_version_ok(self):
|
||||||
sdc = fdroidserver.scanner.SignatureDataController('nnn', 'fff.yml')
|
sdc = fdroidserver.scanner.SignatureDataController('nnn', 'fff.yml', 'https://example.com/test.json')
|
||||||
sdc.data['version'] = fdroidserver.scanner.SCANNER_CACHE_VERSION
|
sdc.data['version'] = fdroidserver.scanner.SCANNER_CACHE_VERSION
|
||||||
sdc.check_data_version()
|
sdc.check_data_version()
|
||||||
|
|
||||||
def test_check_data_version_exception(self):
|
def test_check_data_version_exception(self):
|
||||||
sdc = fdroidserver.scanner.SignatureDataController('nnn', 'fff.yml')
|
sdc = fdroidserver.scanner.SignatureDataController('nnn', 'fff.yml', 'https://example.com/test.json')
|
||||||
with self.assertRaises(fdroidserver.scanner.SignatureDataVersionMismatchException):
|
with self.assertRaises(fdroidserver.scanner.SignatureDataVersionMismatchException):
|
||||||
sdc.check_data_version()
|
sdc.check_data_version()
|
||||||
|
|
||||||
def test_write_to_cache(self):
|
def test_write_to_cache(self):
|
||||||
open_func = mock.mock_open()
|
open_func = mock.mock_open()
|
||||||
sdc = fdroidserver.scanner.SignatureDataController('nnn', 'fff.yml')
|
sdc = fdroidserver.scanner.SignatureDataController('nnn', 'fff.yml', 'https://example.com/test.json')
|
||||||
sdc.data = {"mocked": "data"}
|
sdc.data = {"mocked": "data"}
|
||||||
|
|
||||||
with mock.patch("builtins.open", open_func), mock.patch(
|
with mock.patch("builtins.open", open_func), mock.patch(
|
||||||
@ -565,28 +564,6 @@ class Test_SignatureDataController(unittest.TestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class Test_SUSSDataController_fetch_signatures_from_web(unittest.TestCase):
|
|
||||||
def setUp(self):
|
|
||||||
self.uo_func = mock.Mock(return_value=io.StringIO(textwrap.dedent('''\
|
|
||||||
version: 999
|
|
||||||
timestamp: "1999-12-31T23:59:59.999999+00:00"
|
|
||||||
signatures:
|
|
||||||
- binary_signature: com/google/firebase
|
|
||||||
name: Google Firebase
|
|
||||||
types:
|
|
||||||
- tracker
|
|
||||||
- non-free
|
|
||||||
- gradle_signature: com/google/android/gms
|
|
||||||
name: Google Mobile Services
|
|
||||||
types:
|
|
||||||
- non-free
|
|
||||||
- network_signature: doubleclick\\.net
|
|
||||||
name: Another thing to test.
|
|
||||||
types:
|
|
||||||
- ads
|
|
||||||
''')))
|
|
||||||
|
|
||||||
|
|
||||||
class Test_main(unittest.TestCase):
|
class Test_main(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.args = ["com.example.app", "local/additional.apk", "another.apk"]
|
self.args = ["com.example.app", "local/additional.apk", "another.apk"]
|
||||||
@ -654,7 +631,6 @@ if __name__ == "__main__":
|
|||||||
unittest.makeSuite(ScannerTest),
|
unittest.makeSuite(ScannerTest),
|
||||||
unittest.makeSuite(Test_scan_binary),
|
unittest.makeSuite(Test_scan_binary),
|
||||||
unittest.makeSuite(Test_SignatureDataController),
|
unittest.makeSuite(Test_SignatureDataController),
|
||||||
unittest.makeSuite(Test_SUSSDataController_fetch_signatures_from_web),
|
|
||||||
unittest.makeSuite(Test_main),
|
unittest.makeSuite(Test_main),
|
||||||
])
|
])
|
||||||
unittest.main(failfast=False)
|
unittest.main(failfast=False)
|
||||||
|
Loading…
Reference in New Issue
Block a user