From c452d28088bcf7bec2ef0c2e189e01c635edf374 Mon Sep 17 00:00:00 2001 From: Fabio Zadrozny Date: Thu, 10 Oct 2019 08:09:25 -0300 Subject: [PATCH] pydevd authorize request. Fixes #1829 --- .../_debug_adapter/debugProtocolCustom.json | 184 +++++++++----- .../_debug_adapter/pydevd_schema.py | 235 ++++++++++++++++++ .../pydevd_process_net_command_json.py | 24 +- .../pydevd/tests_python/test_debugger_json.py | 33 +-- 4 files changed, 383 insertions(+), 93 deletions(-) diff --git a/src/ptvsd/_vendored/pydevd/_pydevd_bundle/_debug_adapter/debugProtocolCustom.json b/src/ptvsd/_vendored/pydevd/_pydevd_bundle/_debug_adapter/debugProtocolCustom.json index 3030fc27..288dd654 100644 --- a/src/ptvsd/_vendored/pydevd/_pydevd_bundle/_debug_adapter/debugProtocolCustom.json +++ b/src/ptvsd/_vendored/pydevd/_pydevd_bundle/_debug_adapter/debugProtocolCustom.json @@ -74,76 +74,76 @@ }] }, - "SetPydevdSourceMapRequest": { - "allOf": [ { "$ref": "#/definitions/Request" }, { - "type": "object", - "description": [ - "Sets multiple PydevdSourceMap for a single source and clears all previous PydevdSourceMap in that source.", - "i.e.: Maps paths and lines in a 1:N mapping (use case: map a single file in the IDE to multiple IPython cells).", - "To clear all PydevdSourceMap for a source, specify an empty array.", - "Interaction with breakpoints: When a new mapping is sent, breakpoints that match the source (or previously matched a source) are reapplied.", - "Interaction with launch pathMapping: both mappings are independent. This mapping is applied after the launch pathMapping." - ], - "properties": { - "command": { - "type": "string", - "enum": [ "setPydevdSourceMap" ] - }, - "arguments": { - "$ref": "#/definitions/SetPydevdSourceMapArguments" - } - }, - "required": [ "command", "arguments" ] - }] - }, - "SetPydevdSourceMapArguments": { - "type": "object", - "description": "Arguments for 'setPydevdSourceMap' request.", - "properties": { - "source": { - "$ref": "#/definitions/Source", - "description": "The source location of the PydevdSourceMap; 'source.path' must be specified (e.g.: for an ipython notebook this could be something as /home/notebook/note.py)." - }, - "pydevdSourceMaps": { - "type": "array", - "items": { - "$ref": "#/definitions/PydevdSourceMap" - }, - "description": "The PydevdSourceMaps to be set to the given source (provide an empty array to clear the source mappings for a given path)." - } - }, - "required": [ "source", "pydevdSourceMap" ] - }, - "SetPydevdSourceMapResponse": { - "allOf": [ { "$ref": "#/definitions/Response" }, { - "type": "object", - "description": "Response to 'setPydevdSourceMap' request. This is just an acknowledgement, so no body field is required." - }] - }, + "SetPydevdSourceMapRequest": { + "allOf": [ { "$ref": "#/definitions/Request" }, { + "type": "object", + "description": [ + "Sets multiple PydevdSourceMap for a single source and clears all previous PydevdSourceMap in that source.", + "i.e.: Maps paths and lines in a 1:N mapping (use case: map a single file in the IDE to multiple IPython cells).", + "To clear all PydevdSourceMap for a source, specify an empty array.", + "Interaction with breakpoints: When a new mapping is sent, breakpoints that match the source (or previously matched a source) are reapplied.", + "Interaction with launch pathMapping: both mappings are independent. This mapping is applied after the launch pathMapping." + ], + "properties": { + "command": { + "type": "string", + "enum": [ "setPydevdSourceMap" ] + }, + "arguments": { + "$ref": "#/definitions/SetPydevdSourceMapArguments" + } + }, + "required": [ "command", "arguments" ] + }] + }, + "SetPydevdSourceMapArguments": { + "type": "object", + "description": "Arguments for 'setPydevdSourceMap' request.", + "properties": { + "source": { + "$ref": "#/definitions/Source", + "description": "The source location of the PydevdSourceMap; 'source.path' must be specified (e.g.: for an ipython notebook this could be something as /home/notebook/note.py)." + }, + "pydevdSourceMaps": { + "type": "array", + "items": { + "$ref": "#/definitions/PydevdSourceMap" + }, + "description": "The PydevdSourceMaps to be set to the given source (provide an empty array to clear the source mappings for a given path)." + } + }, + "required": [ "source", "pydevdSourceMap" ] + }, + "SetPydevdSourceMapResponse": { + "allOf": [ { "$ref": "#/definitions/Response" }, { + "type": "object", + "description": "Response to 'setPydevdSourceMap' request. This is just an acknowledgement, so no body field is required." + }] + }, - "PydevdSourceMap": { - "type": "object", - "description": "Information that allows mapping a local line to a remote source/line.", - "properties": { - "line": { - "type": "integer", - "description": "The local line to which the mapping should map to (e.g.: for an ipython notebook this would be the first line of the cell in the file)." - }, - "endLine": { - "type": "integer", - "description": "The end line." - }, - "runtimeSource": { - "$ref": "#/definitions/Source", - "description": "The path that the user has remotely -- 'source.path' must be specified (e.g.: for an ipython notebook this could be something as '')" - }, - "runtimeLine": { - "type": "integer", - "description": "The remote line to which the mapping should map to (e.g.: for an ipython notebook this would be always 1 as it'd map the start of the cell)." - } - }, - "required": ["line", "endLine", "runtimeSource", "runtimeLine"] - }, + "PydevdSourceMap": { + "type": "object", + "description": "Information that allows mapping a local line to a remote source/line.", + "properties": { + "line": { + "type": "integer", + "description": "The local line to which the mapping should map to (e.g.: for an ipython notebook this would be the first line of the cell in the file)." + }, + "endLine": { + "type": "integer", + "description": "The end line." + }, + "runtimeSource": { + "$ref": "#/definitions/Source", + "description": "The path that the user has remotely -- 'source.path' must be specified (e.g.: for an ipython notebook this could be something as '')" + }, + "runtimeLine": { + "type": "integer", + "description": "The remote line to which the mapping should map to (e.g.: for an ipython notebook this would be always 1 as it'd map the start of the cell)." + } + }, + "required": ["line", "endLine", "runtimeSource", "runtimeLine"] + }, "PydevdSystemInfoRequest": { "allOf": [ { "$ref": "#/definitions/Request" }, { @@ -256,6 +256,52 @@ "description": "Integer value indicating the bitness of the current process." } } + }, + "PydevdAuthorizeRequest": { + "allOf": [ { "$ref": "#/definitions/Request" }, { + "type": "object", + "description": "A request to authorize the ide to start accepting commands.", + "properties": { + "command": { + "type": "string", + "enum": [ "pydevdAuthorize" ] + }, + "arguments": { + "$ref": "#/definitions/PydevdAuthorizeArguments" + } + }, + "required": [ "command", "arguments" ] + }] + }, + "PydevdAuthorizeArguments": { + "type": "object", + "description": "Arguments for 'pydevdAuthorize' request.", + "properties": { + "debugServerAccessToken": { + "type": "string" , + "description": "The access token to access the debug server." + } + }, + "required": [ "command" ] + }, + "PydevdAuthorizeResponse": { + "allOf": [ { "$ref": "#/definitions/Response" }, { + "type": "object", + "description": "Response to 'pydevdAuthorize' request.", + "properties": { + "body": { + "type": "object", + "properties": { + "clientAccessToken": { + "type": "string", + "description": "The access token to access the client (i.e.: usually the IDE)." + } + }, + "required": [ "clientAccessToken" ] + } + }, + "required": [ "body" ] + }] } } } \ No newline at end of file diff --git a/src/ptvsd/_vendored/pydevd/_pydevd_bundle/_debug_adapter/pydevd_schema.py b/src/ptvsd/_vendored/pydevd/_pydevd_bundle/_debug_adapter/pydevd_schema.py index c8867a86..6e5b5433 100644 --- a/src/ptvsd/_vendored/pydevd/_pydevd_bundle/_debug_adapter/pydevd_schema.py +++ b/src/ptvsd/_vendored/pydevd/_pydevd_bundle/_debug_adapter/pydevd_schema.py @@ -13971,6 +13971,206 @@ class PydevdProcessInfo(BaseSchema): return dct +@register_request('pydevdAuthorize') +@register +class PydevdAuthorizeRequest(BaseSchema): + """ + A request to authorize the ide to start accepting commands. + + Note: automatically generated code. Do not edit manually. + """ + + __props__ = { + "seq": { + "type": "integer", + "description": "Sequence number." + }, + "type": { + "type": "string", + "enum": [ + "request" + ] + }, + "command": { + "type": "string", + "enum": [ + "pydevdAuthorize" + ] + }, + "arguments": { + "type": "PydevdAuthorizeArguments" + } + } + __refs__ = set(['arguments']) + + __slots__ = list(__props__.keys()) + ['kwargs'] + + def __init__(self, arguments, seq=-1, update_ids_from_dap=False, **kwargs): # noqa (update_ids_from_dap may be unused) + """ + :param string type: + :param string command: + :param PydevdAuthorizeArguments arguments: + :param integer seq: Sequence number. + """ + self.type = 'request' + self.command = 'pydevdAuthorize' + if arguments is None: + self.arguments = PydevdAuthorizeArguments() + else: + self.arguments = PydevdAuthorizeArguments(update_ids_from_dap=update_ids_from_dap, **arguments) if arguments.__class__ != PydevdAuthorizeArguments else arguments + self.seq = seq + self.kwargs = kwargs + + + def to_dict(self, update_ids_to_dap=False): # noqa (update_ids_to_dap may be unused) + type = self.type # noqa (assign to builtin) + command = self.command + arguments = self.arguments + seq = self.seq + dct = { + 'type': type, + 'command': command, + 'arguments': arguments.to_dict(update_ids_to_dap=update_ids_to_dap), + 'seq': seq, + } + dct.update(self.kwargs) + return dct + + +@register +class PydevdAuthorizeArguments(BaseSchema): + """ + Arguments for 'pydevdAuthorize' request. + + Note: automatically generated code. Do not edit manually. + """ + + __props__ = { + "debugServerAccessToken": { + "type": "string", + "description": "The access token to access the debug server." + } + } + __refs__ = set() + + __slots__ = list(__props__.keys()) + ['kwargs'] + + def __init__(self, debugServerAccessToken=None, update_ids_from_dap=False, **kwargs): # noqa (update_ids_from_dap may be unused) + """ + :param string debugServerAccessToken: The access token to access the debug server. + """ + self.debugServerAccessToken = debugServerAccessToken + self.kwargs = kwargs + + + def to_dict(self, update_ids_to_dap=False): # noqa (update_ids_to_dap may be unused) + debugServerAccessToken = self.debugServerAccessToken + dct = { + } + if debugServerAccessToken is not None: + dct['debugServerAccessToken'] = debugServerAccessToken + dct.update(self.kwargs) + return dct + + +@register_response('pydevdAuthorize') +@register +class PydevdAuthorizeResponse(BaseSchema): + """ + Response to 'pydevdAuthorize' request. + + Note: automatically generated code. Do not edit manually. + """ + + __props__ = { + "seq": { + "type": "integer", + "description": "Sequence number." + }, + "type": { + "type": "string", + "enum": [ + "response" + ] + }, + "request_seq": { + "type": "integer", + "description": "Sequence number of the corresponding request." + }, + "success": { + "type": "boolean", + "description": "Outcome of the request." + }, + "command": { + "type": "string", + "description": "The command requested." + }, + "message": { + "type": "string", + "description": "Contains error message if success == false." + }, + "body": { + "type": "object", + "properties": { + "clientAccessToken": { + "type": "string", + "description": "The access token to access the client (i.e.: usually the IDE)." + } + }, + "required": [ + "clientAccessToken" + ] + } + } + __refs__ = set(['body']) + + __slots__ = list(__props__.keys()) + ['kwargs'] + + def __init__(self, request_seq, success, command, body, seq=-1, message=None, update_ids_from_dap=False, **kwargs): # noqa (update_ids_from_dap may be unused) + """ + :param string type: + :param integer request_seq: Sequence number of the corresponding request. + :param boolean success: Outcome of the request. + :param string command: The command requested. + :param PydevdAuthorizeResponseBody body: + :param integer seq: Sequence number. + :param string message: Contains error message if success == false. + """ + self.type = 'response' + self.request_seq = request_seq + self.success = success + self.command = command + if body is None: + self.body = PydevdAuthorizeResponseBody() + else: + self.body = PydevdAuthorizeResponseBody(update_ids_from_dap=update_ids_from_dap, **body) if body.__class__ != PydevdAuthorizeResponseBody else body + self.seq = seq + self.message = message + self.kwargs = kwargs + + + def to_dict(self, update_ids_to_dap=False): # noqa (update_ids_to_dap may be unused) + type = self.type # noqa (assign to builtin) + request_seq = self.request_seq + success = self.success + command = self.command + body = self.body + seq = self.seq + message = self.message + dct = { + 'type': type, + 'request_seq': request_seq, + 'success': success, + 'command': command, + 'body': body.to_dict(update_ids_to_dap=update_ids_to_dap), + 'seq': seq, + } + if message is not None: + dct['message'] = message + dct.update(self.kwargs) + return dct + + @register class ErrorResponseBody(BaseSchema): """ @@ -16029,3 +16229,38 @@ class PydevdSystemInfoResponseBody(BaseSchema): } dct.update(self.kwargs) return dct + + +@register +class PydevdAuthorizeResponseBody(BaseSchema): + """ + "body" of PydevdAuthorizeResponse + + Note: automatically generated code. Do not edit manually. + """ + + __props__ = { + "clientAccessToken": { + "type": "string", + "description": "The access token to access the client (i.e.: usually the IDE)." + } + } + __refs__ = set() + + __slots__ = list(__props__.keys()) + ['kwargs'] + + def __init__(self, clientAccessToken, update_ids_from_dap=False, **kwargs): # noqa (update_ids_from_dap may be unused) + """ + :param string clientAccessToken: The access token to access the client (i.e.: usually the IDE). + """ + self.clientAccessToken = clientAccessToken + self.kwargs = kwargs + + + def to_dict(self, update_ids_to_dap=False): # noqa (update_ids_to_dap may be unused) + clientAccessToken = self.clientAccessToken + dct = { + 'clientAccessToken': clientAccessToken, + } + dct.update(self.kwargs) + return dct diff --git a/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_process_net_command_json.py b/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_process_net_command_json.py index ec4cb6b1..8a544bef 100644 --- a/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_process_net_command_json.py +++ b/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_process_net_command_json.py @@ -15,7 +15,7 @@ from _pydevd_bundle._debug_adapter.pydevd_schema import ( ProcessEvent, Scope, ScopesResponseBody, SetExpressionResponseBody, SetVariableResponseBody, SourceBreakpoint, SourceResponseBody, VariablesResponseBody, SetBreakpointsResponseBody, Response, InitializeRequest, InitializeResponse, - Capabilities) + Capabilities, PydevdAuthorizeRequest) from _pydevd_bundle.pydevd_api import PyDevdAPI from _pydevd_bundle.pydevd_breakpoints import get_exception_class from _pydevd_bundle.pydevd_comm_constants import ( @@ -162,12 +162,10 @@ class PyDevJsonCommandProcessor(object): print('Handled in pydevd: %s (in PyDevJsonCommandProcessor).\n' % (method_name,)) with py_db._main_lock: - if request.__class__ == InitializeRequest: - initialize_request = request # : :type initialize_request: InitializeRequest - pydevd_specific_info = initialize_request.arguments.kwargs.get('pydevd', {}) - if pydevd_specific_info.__class__ == dict: - access_token = pydevd_specific_info.get('debugServerAccessToken') - py_db.authentication.login(access_token) + if request.__class__ == PydevdAuthorizeRequest: + authorize_request = request # : :type authorize_request: PydevdAuthorizeRequest + access_token = authorize_request.arguments.debugServerAccessToken + py_db.authentication.login(access_token) if not py_db.authentication.is_authenticated(): response = Response( @@ -180,6 +178,15 @@ class PyDevJsonCommandProcessor(object): if cmd is not None and send_response: py_db.writer.add_command(cmd) + def on_pydevdauthorize_request(self, py_db, request): + ide_access_token = py_db.authentication.ide_access_token + body = {'clientAccessToken': None} + if ide_access_token: + body['clientAccessToken'] = ide_access_token + + response = pydevd_base_schema.build_response(request, kwargs={'body': body}) + return NetCommand(CMD_RETURN, 0, response, is_json=True) + def on_initialize_request(self, py_db, request): body = Capabilities( # Supported. @@ -224,11 +231,8 @@ class PyDevJsonCommandProcessor(object): # Non-standard capabilities/info below. body['supportsDebuggerProperties'] = True - ide_access_token = py_db.authentication.ide_access_token body['pydevd'] = pydevd_info = {} pydevd_info['processId'] = os.getpid() - if ide_access_token: - pydevd_info['ideAccessToken'] = ide_access_token self.api.notify_initialize(py_db) response = pydevd_base_schema.build_response(request, kwargs={'body': body}) return NetCommand(CMD_RETURN, 0, response, is_json=True) diff --git a/src/ptvsd/_vendored/pydevd/tests_python/test_debugger_json.py b/src/ptvsd/_vendored/pydevd/tests_python/test_debugger_json.py index c53b2d64..58750094 100644 --- a/src/ptvsd/_vendored/pydevd/tests_python/test_debugger_json.py +++ b/src/ptvsd/_vendored/pydevd/tests_python/test_debugger_json.py @@ -409,12 +409,9 @@ class JsonFacade(object): assert response.success == success return response - def write_initialize(self, access_token, success=True): + def write_initialize(self, success=True): arguments = InitializeRequestArguments( adapterID='pydevd_test_case', - pydevd=dict( - debugServerAccessToken=access_token, - ) ) response = self.wait_for_response(self.write_request(InitializeRequest(arguments))) assert response.success == success @@ -423,6 +420,16 @@ class JsonFacade(object): assert isinstance(process_id, int) return response + def write_authorize(self, access_token, success=True): + from _pydevd_bundle._debug_adapter.pydevd_schema import PydevdAuthorizeArguments + from _pydevd_bundle._debug_adapter.pydevd_schema import PydevdAuthorizeRequest + arguments = PydevdAuthorizeArguments( + debugServerAccessToken=access_token, + ) + response = self.wait_for_response(self.write_request(PydevdAuthorizeRequest(arguments))) + assert response.success == success + return response + def evaluate(self, expression, frameId=None, context=None, fmt=None, success=True): eval_request = self.write_request( pydevd_schema.EvaluateRequest(pydevd_schema.EvaluateArguments( @@ -3036,7 +3043,7 @@ def test_terminate(case_setup, scenario, check_subprocesses): json_facade.write_launch(terminateChildProcesses=False) json_facade.write_make_initial_run() - response = json_facade.write_initialize(None) + response = json_facade.write_initialize() pid = response.to_dict()['body']['pydevd']['processId'] if check_subprocesses in ('kill_subprocesses', 'dont_kill_subprocesses', 'kill_subprocesses_ignore_pid'): @@ -3141,17 +3148,15 @@ def test_access_token(case_setup): response = json_facade.write_set_debugger_property(multi_threads_single_notification=True, success=False) assert response.message == "Client not authenticated." - response = json_facade.write_initialize(access_token='wrong', success=False) + response = json_facade.write_authorize(access_token='wrong', success=False) assert response.message == "Client not authenticated." response = json_facade.write_set_debugger_property(multi_threads_single_notification=True, success=False) assert response.message == "Client not authenticated." - response = json_facade.write_initialize(access_token='bar123', success=True) - # : :type response:InitializeResponse - initialize_response_body = response.body - # : :type initialize_response_body:Capabilities - assert initialize_response_body.kwargs['pydevd']['ideAccessToken'] == 'foo321' + authorize_response = json_facade.write_authorize(access_token='bar123', success=True) + # : :type authorize_response:PydevdAuthorizeResponse + assert authorize_response.body.clientAccessToken == 'foo321' json_facade.write_set_debugger_property(multi_threads_single_notification=True) json_facade.write_launch() @@ -3170,11 +3175,11 @@ def test_access_token(case_setup): json_facade.write_disconnect() - response = json_facade.write_initialize(access_token='wrong', success=False) + response = json_facade.write_authorize(access_token='wrong', success=False) assert response.message == "Client not authenticated." - response = json_facade.write_initialize(access_token='bar123') - assert initialize_response_body.kwargs['pydevd']['ideAccessToken'] == 'foo321' + authorize_response = json_facade.write_authorize(access_token='bar123') + assert authorize_response.body.clientAccessToken == 'foo321' json_facade.write_set_breakpoints(break_line) json_hit = json_facade.wait_for_thread_stopped(line=break_line)