From d504eeb795e2c384fe055b0523de6a68e8b42e7c Mon Sep 17 00:00:00 2001 From: Kenneth Wilke Date: Fri, 1 Jul 2022 11:16:39 -0500 Subject: [PATCH] Added path parameter escaping This change filters URL path parameters through urllib.parse.quote to sanitize inputs that are formatted into the url paths. This mitigates the risk of path traversal attacks. This also adds `administered` to the supported scopes for the newly added /administered/identities/me (`getAdministeredIdentitiesMe`) operation. This change also fixes a mix of tabs and spaces in the `async_function_template` template. --- generator/async_class_template.jinja2 | 3 +++ generator/async_function_template.jinja2 | 17 ++++++++++------- generator/batch_class_template.jinja2 | 3 +++ generator/batch_function_template.jinja2 | 3 +++ generator/class_template.jinja2 | 3 +++ generator/function_template.jinja2 | 3 +++ generator/generate_library.py | 9 +++++++-- 7 files changed, 32 insertions(+), 9 deletions(-) diff --git a/generator/async_class_template.jinja2 b/generator/async_class_template.jinja2 index f20463ed..e9007920 100644 --- a/generator/async_class_template.jinja2 +++ b/generator/async_class_template.jinja2 @@ -1,3 +1,6 @@ +import urllib + + class Async{{ class_name }}: def __init__(self, session): super().__init__() diff --git a/generator/async_function_template.jinja2 b/generator/async_function_template.jinja2 index e3b1392b..fcdf7d79 100644 --- a/generator/async_function_template.jinja2 +++ b/generator/async_function_template.jinja2 @@ -15,15 +15,18 @@ {% if assert_blocks|length > 0 %} {% for param, values in assert_blocks %} if '{{ param }}' in kwargs: - options = {{ values }} - assert kwargs['{{ param }}'] in options, f'''"{{ param }}" cannot be "{kwargs['{{ param }}']}", & must be set to one of: {options}''' + options = {{ values }} + assert kwargs['{{ param }}'] in options, f'''"{{ param }}" cannot be "{kwargs['{{ param }}']}", & must be set to one of: {options}''' {% endfor %} {% endif %} metadata = { - 'tags': {{ tags }}, - 'operation': '{{ operation }}' + 'tags': {{ tags }}, + 'operation': '{{ operation }}' } + {% for param in path_params %} + {{ param }} = urllib.parse.quote({{ param }}, safe='') + {% endfor %} resource = f'{{ resource }}' {% if query_params|length > 0 %} @@ -34,9 +37,9 @@ {% if array_params|length > 0 %} array_params = [{% for param in array_params %}'{{ param }}', {% endfor %}] for k, v in kwargs.items(): - if k.strip() in array_params: - params[f'{k.strip()}[]'] = kwargs[f'{k}'] - params.pop(k.strip()) + if k.strip() in array_params: + params[f'{k.strip()}[]'] = kwargs[f'{k}'] + params.pop(k.strip()) {% endif %} {% if body_params|length > 0 %} diff --git a/generator/batch_class_template.jinja2 b/generator/batch_class_template.jinja2 index bfd80743..681e6d2c 100644 --- a/generator/batch_class_template.jinja2 +++ b/generator/batch_class_template.jinja2 @@ -1,3 +1,6 @@ +import urllib + + class ActionBatch{{ class_name }}(object): def __init__(self): super(ActionBatch{{ class_name }}, self).__init__() diff --git a/generator/batch_function_template.jinja2 b/generator/batch_function_template.jinja2 index 4935eb3b..99b0db31 100644 --- a/generator/batch_function_template.jinja2 +++ b/generator/batch_function_template.jinja2 @@ -24,6 +24,9 @@ 'tags': {{ tags }}, 'operation': '{{ operation }}' } + {% for param in path_params %} + {{ param }} = urllib.parse.quote({{ param }}, safe='') + {% endfor %} resource = f'{{ resource }}' {% if query_params|length > 0 %} diff --git a/generator/class_template.jinja2 b/generator/class_template.jinja2 index 875d2003..983ff759 100644 --- a/generator/class_template.jinja2 +++ b/generator/class_template.jinja2 @@ -1,3 +1,6 @@ +import urllib + + class {{ class_name }}(object): def __init__(self, session): super({{ class_name }}, self).__init__() diff --git a/generator/function_template.jinja2 b/generator/function_template.jinja2 index be4c7aa6..0c1e9db7 100644 --- a/generator/function_template.jinja2 +++ b/generator/function_template.jinja2 @@ -24,6 +24,9 @@ 'tags': {{ tags }}, 'operation': '{{ operation }}' } + {% for param in path_params %} + {{ param }} = urllib.parse.quote({{ param }}, safe='') + {% endfor %} resource = f'{{ resource }}' {% if query_params|length > 0 %} diff --git a/generator/generate_library.py b/generator/generate_library.py index 1bfd636c..15f1bbe8 100644 --- a/generator/generate_library.py +++ b/generator/generate_library.py @@ -126,7 +126,7 @@ def parse_params(operation, parameters, param_filters=[]): def generate_library(spec, version_number): # Supported scopes list will include organizations, networks, devices, and all product types. supported_scopes = ['organizations', 'networks', 'devices', 'appliance', 'camera', 'cellularGateway', 'insight', - 'sm', 'switch', 'wireless', 'sensor'] + 'sm', 'switch', 'wireless', 'sensor', 'administered'] # legacy scopes = ['organizations', 'networks', 'devices', 'appliance', 'camera', 'cellularGateway', 'insight', # 'sm', 'switch', 'wireless'] tags = spec['tags'] @@ -269,10 +269,11 @@ def generate_library(spec, version_number): assert_blocks.append((p, values['enum'])) # Function body for GET endpoints - query_params = array_params = body_params = {} + query_params = array_params = body_params = path_params = {} if method == 'get': query_params = parse_params(operation, parameters, 'query') array_params = parse_params(operation, parameters, 'array') + path_params = parse_params(operation, parameters, 'path') pagination_params = parse_params(operation, parameters, 'pagination') if query_params or array_params: if pagination_params: @@ -288,6 +289,7 @@ def generate_library(spec, version_number): # Function body for POST/PUT endpoints elif method == 'post' or method == 'put': body_params = parse_params(operation, parameters, 'body') + path_params = parse_params(operation, parameters, 'path') if body_params: call_line = f'return self._session.{method}(metadata, resource, payload)' else: @@ -295,6 +297,7 @@ def generate_library(spec, version_number): # Function body for DELETE endpoints elif method == 'delete': + path_params = parse_params(operation, parameters, 'path') call_line = 'return self._session.delete(metadata, resource)' # Add function to files @@ -317,6 +320,7 @@ def generate_library(spec, version_number): query_params=query_params, array_params=array_params, body_params=body_params, + path_params=path_params, call_line=call_line ) ) @@ -336,6 +340,7 @@ def generate_library(spec, version_number): query_params=query_params, array_params=array_params, body_params=body_params, + path_params=path_params, call_line=call_line ) )