Skip to content

Commit c840fd3

Browse files
committed
verify modules
1 parent 17615ac commit c840fd3

6 files changed

Lines changed: 82 additions & 0 deletions

File tree

app/objects/c_obfuscator.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
from app.objects.interfaces.i_object import FirstClassObjectInterface
77
from app.utility.base_object import BaseObject
8+
from app.utility.base_world import BaseWorld
89

910

1011
class ObfuscatorSchema(ma.Schema):
@@ -28,6 +29,7 @@ def unique(self):
2829

2930
def __init__(self, name, description, module):
3031
super().__init__()
32+
BaseWorld.verify_module(module, 'obfuscators')
3133
self.name = name
3234
self.description = description
3335
self.module = module

app/objects/c_planner.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
from app.objects.interfaces.i_object import FirstClassObjectInterface
66
from app.utility.base_object import BaseObject
7+
from app.utility.base_world import BaseWorld
78
from app.objects.secondclass.c_fact import Fact, FactSchema
89

910

@@ -43,6 +44,7 @@ def __init__(self, name='', planner_id='', module='', params=None, stopping_cond
4344
super().__init__()
4445
self.name = name
4546
self.planner_id = planner_id if planner_id else str(uuid.uuid4())
47+
BaseWorld.verify_module(module, 'planners')
4648
self.module = module
4749
self.params = params if params else {}
4850
self.description = description

app/objects/secondclass/c_parser.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from app.objects.secondclass.c_parserconfig import ParserConfig, ParserConfigSchema
44
from app.utility.base_object import BaseObject
5+
from app.utility.base_world import BaseWorld
56

67

78
class ParserSchema(ma.Schema):
@@ -31,5 +32,6 @@ def unique(self):
3132

3233
def __init__(self, module, parserconfigs):
3334
super().__init__()
35+
BaseWorld.verify_module(module, 'parsers', ['app/learning'])
3436
self.module = module
3537
self.parserconfigs = parserconfigs

app/objects/secondclass/c_requirement.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import marshmallow as ma
22

33
from app.utility.base_object import BaseObject
4+
from app.utility.base_world import BaseWorld
45

56

67
class RequirementSchema(ma.Schema):
@@ -23,5 +24,6 @@ def unique(self):
2324

2425
def __init__(self, module, relationship_match):
2526
super().__init__()
27+
BaseWorld.verify_module(module, 'requirements')
2628
self.module = module
2729
self.relationship_match = relationship_match

app/utility/base_world.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import binascii
2+
import os
23
import string
34
import re
45
import yaml
@@ -164,6 +165,35 @@ def parse_version(version_string, pattern=r'([0-9]+(?:\.[0-9]+)+)'):
164165
logging.getLogger('check_requirement').error(repr(e))
165166
return False
166167

168+
@staticmethod
169+
def verify_module(module, module_type, additional_paths=[]):
170+
'''Checks that the specified module matches the expected module path
171+
given its type and that the corresponding Python file exists.
172+
Raises a ModuleNotFoundError if the requested module does not meet the
173+
required conditions.'''
174+
175+
module_path = module.replace('.', '/') + '.py'
176+
module_filename = os.path.basename(module_path)
177+
allowed_paths = [os.path.join('app/', module_type, module_filename)]
178+
if os.path.isdir('plugins'):
179+
for plugin_dir in os.listdir('plugins'):
180+
if os.path.isdir(os.path.join('plugins', plugin_dir)):
181+
allowed_paths.append(
182+
os.path.join('plugins', plugin_dir, 'app', module_type, module_filename)
183+
)
184+
if additional_paths:
185+
allowed_paths += [os.path.join(p, module_filename) for p in additional_paths]
186+
if module_path not in allowed_paths:
187+
raise ModuleNotFoundError(
188+
f'Module {module} does not align with allowed paths for this module type. Allowed paths for this module: {str(allowed_paths)}',
189+
name=module
190+
)
191+
if not os.path.isfile(module_path):
192+
raise ModuleNotFoundError(
193+
f'Module {module} with path {module_path} was not found on disk.',
194+
name=module
195+
)
196+
167197
class Access(Enum):
168198
APP = 0
169199
RED = 1

tests/utility/test_base_world.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import pytest
2+
import os
23
import yaml
34

45
from datetime import datetime, timezone
6+
from unittest import mock
57
from app.utility.base_world import BaseWorld
68

79

@@ -91,3 +93,45 @@ def test_is_not_base64(self):
9193
def test_is_base64(self):
9294
b64str = 'aGVsbG8gd29ybGQgZnJvbSB1bml0IHRlc3QgbGFuZAo='
9395
assert BaseWorld.is_base64(b64str)
96+
97+
@mock.patch.object(os, 'listdir', return_value=['stockpile', 'testplugin', 'dummy'])
98+
@mock.patch.object(os.path, 'isdir', return_value=True)
99+
def test_verify_module(self, mock_isdir, mock_listdir):
100+
def _mock_isfile(path):
101+
return path in [
102+
'plugins/stockpile/app/requirements/test_req.py',
103+
'plugins/testplugin/app/obfuscators/test_obf.py',
104+
'app/planners/test_planner.py',
105+
'app/parsers/test_parser.py',
106+
'app/learning/learning_parser.py',
107+
]
108+
109+
with mock.patch.object(os.path, 'isfile', side_effect=_mock_isfile):
110+
BaseWorld.verify_module('plugins.stockpile.app.requirements.test_req', 'requirements')
111+
BaseWorld.verify_module('plugins.testplugin.app.obfuscators.test_obf', 'obfuscators')
112+
BaseWorld.verify_module('app.planners.test_planner', 'planners')
113+
BaseWorld.verify_module('app.parsers.test_parser', 'parsers')
114+
BaseWorld.verify_module('app.learning.learning_parser', 'parsers', ['app/learning'])
115+
116+
allowed_paths_str = str([
117+
'app/parsers/myparser.py',
118+
'plugins/stockpile/parsers/myparser.py',
119+
'plugins/testplugin/parsers/myparser.py',
120+
'plugins/dummy/parsers/myparser.py'
121+
])
122+
expected_err = f'Module data.payloads.myparser does not align with allowed paths for this module type. Allowed paths for this module: {allowed_paths_str}'
123+
with pytest.raises(ModuleNotFoundError, match=expected_err):
124+
BaseWorld.verify_module('data.payloads.myparser', 'parsers')
125+
allowed_paths_str = str([
126+
'otherdir/myparser.py',
127+
'app/parsers/myparser.py',
128+
'plugins/stockpile/parsers/myparser.py',
129+
'plugins/testplugin/parsers/myparser.py',
130+
'plugins/dummy/parsers/myparser.py'
131+
])
132+
expected_err = f'Module plugins.dne.myparser does not align with allowed paths for this module type. Allowed paths for this module: {allowed_paths_str}'
133+
with pytest.raises(ModuleNotFoundError, match=expected_err):
134+
BaseWorld.verify_module('data.payloads.myparser', 'parsers', ['otherdir'])
135+
expected_err = 'Module plugins.stockpile.app.obfuscators.dne with path plugins/stockpile/app/obfuscators/dne was not found on disk.'
136+
with pytest.raises(ModuleNotFoundError, match=expected_err):
137+
BaseWorld.verify_module('plugins.stockpile.app.obfuscators.dne', 'obfuscators', ['otherdir'])

0 commit comments

Comments
 (0)