Skip to content

Commit

Permalink
Provide access tokens in debugger. Fixes microsoft#1710
Browse files Browse the repository at this point in the history
  • Loading branch information
fabioz committed Aug 27, 2019
1 parent 5f2cb30 commit 5d07e88
Show file tree
Hide file tree
Showing 10 changed files with 367 additions and 67 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@
CMD_MODULE_EVENT = 203
CMD_PROCESS_EVENT = 204

CMD_AUTHENTICATE = 205

CMD_VERSION = 501
CMD_RETURN = 502
CMD_SET_PROTOCOL = 503
Expand Down Expand Up @@ -171,6 +173,8 @@
'203': 'CMD_MODULE_EVENT',
'204': 'CMD_PROCESS_EVENT', # DAP process event.

'205': 'CMD_AUTHENTICATE',

'501': 'CMD_VERSION',
'502': 'CMD_RETURN',
'503': 'CMD_SET_PROTOCOL',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ def handle_argv(self, argv, i, setup):
setup[self.arg_name] = val
del argv[i]


class ArgHandlerBool:
'''
If a given flag is received, mark it as 'True' in setup.
Expand All @@ -51,24 +52,28 @@ def handle_argv(self, argv, i, setup):
ArgHandlerWithParam('port', int, 0),
ArgHandlerWithParam('vm_type'),
ArgHandlerWithParam('client'),
ArgHandlerWithParam('access-token'),
ArgHandlerWithParam('ide-access-token'),

ArgHandlerBool('server'),
ArgHandlerBool('DEBUG_RECORD_SOCKET_READS'),
ArgHandlerBool('multiproc'), # Used by PyCharm (reuses connection: ssh tunneling)
ArgHandlerBool('multiprocess'), # Used by PyDev (creates new connection to ide)
ArgHandlerBool('multiproc'), # Used by PyCharm (reuses connection: ssh tunneling)
ArgHandlerBool('multiprocess'), # Used by PyDev (creates new connection to ide)
ArgHandlerBool('save-signatures'),
ArgHandlerBool('save-threading'),
ArgHandlerBool('save-asyncio'),
ArgHandlerBool('print-in-debugger-startup'),
ArgHandlerBool('cmd-line'),
ArgHandlerBool('module'),
ArgHandlerBool('json-dap'), # Protocol used by ptvsd to communicate with pydevd
ArgHandlerBool('json-dap'), # Protocol used by ptvsd to communicate with pydevd (a single json message in each read)
ArgHandlerBool('json-dap-http'), # Actual DAP (json messages over http protocol).
]

ARGV_REP_TO_HANDLER = {}
for handler in ACCEPTED_ARG_HANDLERS:
ARGV_REP_TO_HANDLER[handler.arg_v_rep] = handler


def get_pydevd_file():
import pydevd
f = pydevd.__file__
Expand All @@ -78,6 +83,7 @@ def get_pydevd_file():
f = f[:-len('$py.class')] + '.py'
return f


def setup_to_argv(setup):
'''
:param dict setup:
Expand All @@ -92,6 +98,7 @@ def setup_to_argv(setup):
handler.to_argv(ret, setup)
return ret


def process_command_line(argv):
""" parses the arguments.
removes our arguments from the command line """
Expand Down Expand Up @@ -135,7 +142,7 @@ def process_command_line(argv):
# --file is special because it's the last one (so, no handler for it).
del argv[i]
setup['file'] = argv[i]
i = len(argv) # pop out, file is our last argument
i = len(argv) # pop out, file is our last argument

elif argv[i] == '--DEBUG':
from pydevd import set_debug
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@
from _pydevd_bundle.pydevd_comm import (pydevd_find_thread_by_id,
InternalEvaluateConsoleExpression, InternalConsoleGetCompletions, InternalRunCustomOperation,
internal_get_next_statement_targets)
from _pydevd_bundle.pydevd_constants import IS_PY3K, NEXT_VALUE_SEPARATOR, IS_WINDOWS
from _pydevd_bundle.pydevd_comm_constants import ID_TO_MEANING, CMD_EXEC_EXPRESSION
from _pydevd_bundle.pydevd_constants import IS_PY3K, NEXT_VALUE_SEPARATOR, IS_WINDOWS, IS_PY2
from _pydevd_bundle.pydevd_comm_constants import ID_TO_MEANING, CMD_EXEC_EXPRESSION, CMD_AUTHENTICATE
from _pydevd_bundle.pydevd_api import PyDevdAPI
from _pydev_bundle.pydev_imports import StringIO
from _pydevd_bundle.pydevd_net_command import NetCommand


class _PyDevCommandProcessor(object):
Expand All @@ -29,6 +30,14 @@ def process_net_command(self, py_db, cmd_id, seq, text):
@param seq: the sequence of the command
@param text: the text received in the command
'''

# We can only proceed if the client is already authenticated or if it's the
# command to authenticate.
if cmd_id != CMD_AUTHENTICATE and not py_db.authentication.is_authenticated():
cmd = py_db.cmd_factory.make_error_message(seq, 'Client not authenticated.')
py_db.writer.add_command(cmd)
return

meaning = ID_TO_MEANING[str(cmd_id)]

# print('Handling %s (%s)' % (meaning, text))
Expand All @@ -42,28 +51,33 @@ def process_net_command(self, py_db, cmd_id, seq, text):
py_db.writer.add_command(cmd)
return

py_db._main_lock.acquire()
try:
cmd = on_command(py_db, cmd_id, seq, text)
if cmd is not None:
py_db.writer.add_command(cmd)
except:
if traceback is not None and sys is not None and pydev_log_exception is not None:
pydev_log_exception()

stream = StringIO()
traceback.print_exc(file=stream)
cmd = py_db.cmd_factory.make_error_message(
seq,
"Unexpected exception in process_net_command.\nInitial params: %s. Exception: %s" % (
((cmd_id, seq, text), stream.getvalue())
)
)
with py_db._main_lock:
try:
cmd = on_command(py_db, cmd_id, seq, text)
if cmd is not None:
py_db.writer.add_command(cmd)
except:
if traceback is not None and sys is not None and pydev_log_exception is not None:
pydev_log_exception()

stream = StringIO()
traceback.print_exc(file=stream)
cmd = py_db.cmd_factory.make_error_message(
seq,
"Unexpected exception in process_net_command.\nInitial params: %s. Exception: %s" % (
((cmd_id, seq, text), stream.getvalue())
)
)
if cmd is not None:
py_db.writer.add_command(cmd)

finally:
py_db._main_lock.release()
def cmd_authenticate(self, py_db, cmd_id, seq, text):
access_token = text
py_db.authentication.login(access_token)
if py_db.authentication.is_authenticated():
return NetCommand(cmd_id, seq, py_db.authentication.ide_access_token)

return py_db.cmd_factory.make_error_message(seq, 'Client not authenticated.')

def cmd_run(self, py_db, cmd_id, seq, text):
return self.api.run(py_db)
Expand Down Expand Up @@ -97,6 +111,9 @@ def cmd_thread_suspend(self, py_db, cmd_id, seq, text):
return self.api.request_suspend_thread(py_db, text.strip())

def cmd_version(self, py_db, cmd_id, seq, text):
if IS_PY2 and isinstance(text, unicode):
text = text.encode('utf-8')

# Default based on server process (although ideally the IDE should
# provide it).
if IS_WINDOWS:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
GotoTargetsResponseBody, ModulesResponseBody, ProcessEventBody,
ProcessEvent, Scope, ScopesResponseBody, SetExpressionResponseBody,
SetVariableResponseBody, SourceBreakpoint, SourceResponseBody,
VariablesResponseBody, SetBreakpointsResponseBody)
VariablesResponseBody, SetBreakpointsResponseBody, Response, InitializeRequest)
from _pydevd_bundle.pydevd_api import PyDevdAPI
from _pydevd_bundle.pydevd_breakpoints import get_exception_class
from _pydevd_bundle.pydevd_comm_constants import (
Expand Down Expand Up @@ -161,6 +161,20 @@ def on_request(py_db, request):
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 not py_db.authentication.is_authenticated():
response = Response(
request.seq, success=False, command=request.command, message='Client not authenticated.', body={})
cmd = NetCommand(CMD_RETURN, 0, response, is_json=True)
py_db.writer.add_command(cmd)
return

cmd = on_request(py_db, request)
if cmd is not None and send_response:
py_db.writer.add_command(cmd)
Expand All @@ -170,7 +184,7 @@ def on_initialize_request(self, py_db, request):
'supportsCompletionsRequest': True,
'supportsConditionalBreakpoints': True,
'supportsConfigurationDoneRequest': True,
'supportsDebuggerProperties': True,
'supportsDebuggerProperties': True,
'supportsDelayedStackTraceLoading': True,
'supportsEvaluateForHovers': True,
'supportsExceptionInfoRequest': True,
Expand All @@ -188,6 +202,10 @@ def on_initialize_request(self, py_db, request):
{'filter': 'uncaught', 'label': 'Uncaught Exceptions', 'default': True},
],
}

ide_access_token = py_db.authentication.ide_access_token
if ide_access_token:
body['pydevd'] = {'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)
Expand Down
Loading

0 comments on commit 5d07e88

Please sign in to comment.