diff --git a/atom_tools/lib/converter.py b/atom_tools/lib/converter.py index cf30f5d..7511a4e 100644 --- a/atom_tools/lib/converter.py +++ b/atom_tools/lib/converter.py @@ -28,7 +28,6 @@ class OpenAPI: Attributes: usages (AtomSlice): The usage slice. - origin_type (str): The origin type. openapi_version (str): The OpenAPI version. title (str): The title for the OpenAPI document file_endpoint_map (dict): Stores the originating filename for endpoints @@ -64,8 +63,7 @@ def __init__( origin_type: str, usages: str, ) -> None: - self.usages: AtomSlice = AtomSlice(usages) - self.origin_type = origin_type + self.usages: AtomSlice = AtomSlice(usages, origin_type) self.openapi_version = dest_format.replace('openapi', '') self.title = f'OpenAPI Specification for {Path(usages).parent.stem}' self.file_endpoint_map: Dict = {} @@ -144,14 +142,14 @@ def process_methods(self) -> Dict[str, List[str]]: Create a dictionary of full names and their corresponding methods. """ method_map = self._process_methods_helper( - 'objectSlices[].{fullName: fullName, resolvedMethods: usages[].*.resolvedMethod[]}') + 'objectSlices[].{full_name: fullName, resolved_methods: usages[].*.resolvedMethod[]}') calls = self._process_methods_helper( - 'objectSlices[].{fullName: fullName, resolvedMethods: usages[].*[][?resolvedMethod].' - 'resolvedMethod[]}') + 'objectSlices[].{full_name: fullName, resolved_methods: usages[].*[?resolvedMethod][]' + '[].resolvedMethod[]}') user_defined_types = self._process_methods_helper( - 'userDefinedTypes[].{fullName: name, resolvedMethods: fields[].name}') + 'userDefinedTypes[].{full_name: name, resolved_methods: fields[].name}') for key, value in calls.items(): if method_map.get(key): @@ -199,7 +197,7 @@ def _query_calls_helper(self, full_name: str) -> List[Dict]: Returns: list: The result of searching for the calls pattern in the usages. """ - pattern = f'objectSlices[?fullName==`{json.dumps(full_name)}`].usages[].*[][]' + pattern = f'objectSlices[?fullName==`{json.dumps(full_name)}`].usages[].*[?callName][][]' compiled_pattern = jmespath.compile(pattern) return compiled_pattern.search(self.usages.content) @@ -297,13 +295,13 @@ def _process_methods_helper(self, pattern: str) -> Dict[str, Any]: dict_resolved_pattern = jmespath.compile(pattern) result = [ i for i in dict_resolved_pattern.search(self.usages.content) - if i.get('resolvedMethods') + if i.get('resolved_methods') ] resolved: Dict = {} for r in result: - full_name = r['fullName'] - methods = r['resolvedMethods'] + full_name = r['full_name'] + methods = r['resolved_methods'] resolved.setdefault(full_name, {'resolved_methods': []})[ 'resolved_methods'].extend(methods) @@ -406,9 +404,9 @@ def _paths_object_helper( def _extract_params(self, ep: str) -> Tuple[str, bool, List]: tmp_params: List = [] py_special_case = False - if self.origin_type in ('js', 'ts', 'javascript', 'typescript'): + if self.usages.origin_type in ('js', 'ts', 'javascript', 'typescript'): ep = js_helper(ep) - elif self.origin_type in ('py', 'python'): + elif self.usages.origin_type in ('py', 'python'): ep, tmp_params = py_helper(ep) py_special_case = True return ep, py_special_case, tmp_params @@ -473,6 +471,7 @@ def calls_to_params(self, ep: str, orig_ep: str, call: Dict | None) -> Dict[str, Args: call (dict): The call object ep (str): The endpoint + orig_ep (str): The original endpoint Returns: dict: The operation object """ @@ -542,7 +541,7 @@ def _filter_matches(self, matches: List[str], code: str) -> List[str]: """ filtered_matches: List[str] = [] - match self.origin_type: + match self.usages.origin_type: case 'java' | 'jar': if not ( code.startswith('@') diff --git a/atom_tools/lib/slices.py b/atom_tools/lib/slices.py index 2ff0f34..9a5a9e5 100644 --- a/atom_tools/lib/slices.py +++ b/atom_tools/lib/slices.py @@ -4,7 +4,6 @@ import json import logging -import re logger = logging.getLogger(__name__) @@ -18,14 +17,16 @@ class AtomSlice: Attributes: content (dict): The dictionary loaded from the usages JSON file. + slice_type (str): The type of slice. + origin_type (str): The originating language. Methods: import_slice: Imports a slice from a JSON file. """ - def __init__(self, filename: str) -> None: - self.content = self.import_slice(filename) - self.endpoints_regex = re.compile(r'[\'"](\S*?)[\'"]', re.IGNORECASE) + def __init__(self, filename: str, origin_type: str) -> None: + self.content, self.slice_type = self.import_slice(filename) + self.origin_type = origin_type @staticmethod def import_slice(filename): @@ -47,20 +48,21 @@ def import_slice(filename): If the JSON file is not a valid usage slice, a warning is logged. """ if not filename: + logger.warning('No filename specified.') return {} try: with open(filename, 'r', encoding='utf-8') as f: content = json.load(f) if content.get('objectSlices'): - return content + return content, 'usages' + if content.get('reachables'): + return content, 'reachables' except (json.decoder.JSONDecodeError, UnicodeDecodeError): logger.warning( - f'Failed to load usages slice: {filename}\nPlease check ' - f'that you specified a valid json file.') + f'Failed to load usages slice: {filename}\nPlease check that you specified a valid' + f' json file.' + ) except FileNotFoundError: - logger.warning( - f'Failed to locate the usages slice file in the location ' - f'specified: {filename}') - - logger.warning('Failed to load usages slice.') - return {} + logger.warning(f'Failed to locate the following slice file: {filename}') + logger.warning('Slice type not recognized.') + return {}, 'unknown' diff --git a/test/test_slices.py b/test/test_slices.py index 233e665..079431a 100644 --- a/test/test_slices.py +++ b/test/test_slices.py @@ -4,51 +4,48 @@ @fixture def java_usages_1(): - return AtomSlice('test/data/java-piggymetrics-usages.json') + return AtomSlice('test/data/java-piggymetrics-usages.json', 'java') @fixture def java_usages_2(): - return AtomSlice('test/data/java-sec-code-usages.json') + return AtomSlice('test/data/java-sec-code-usages.json', 'java') @fixture def js_usages_1(): - return AtomSlice('test/data/js-juiceshop-usages.json') + return AtomSlice('test/data/js-juiceshop-usages.json', 'js') @fixture def js_usages_2(): - return AtomSlice('test/data/js-nodegoat-usages.json') + return AtomSlice('test/data/js-nodegoat-usages.json', 'js') @fixture def py_usages_1(): - return AtomSlice('test/data/py-depscan-usages.json') + return AtomSlice('test/data/py-depscan-usages.json', 'js') @fixture def py_usages_2(): - return AtomSlice('test/data/py-tornado-usages.json') + return AtomSlice('test/data/py-tornado-usages.json', 'js') def test_usages_class( java_usages_1, java_usages_2, ): - usages = AtomSlice('test/data/java-piggymetrics-usages.json') + usages = AtomSlice('test/data/java-piggymetrics-usages.json', 'java') assert usages.content is not None - usages = AtomSlice('test/data/java-sec-code-usages.json') + usages = AtomSlice('test/data/java-sec-code-usages.json', 'java') assert usages.content is not None def test_import_slices(js_usages_1): # Test nonexistent file - assert AtomSlice('test/data/js-tornado-usages.json').content == {} + assert AtomSlice('test/data/js-tornado-usages.json', 'js').content == {} # Test invalid JSON file - assert AtomSlice('test/data/invalid.json').content == {} - - # Valid JSON but reachables not usages - assert AtomSlice('test/data/js-juiceshop-reachables.json').content == {} + assert AtomSlice('test/data/invalid.json', 'js').content == {}