[Core] Raise AttributeError on RPC call to invalid function

Also catch and log errors in rcpserver.sendData
This commit is contained in:
bendikro 2015-07-13 18:15:16 +02:00 committed by Calum Lind
parent 382a99ad61
commit a987c3ed39
3 changed files with 83 additions and 37 deletions

View File

@ -142,7 +142,12 @@ class DelugeRPCProtocol(DelugeTransferProtocol):
:type data: object :type data: object
""" """
self.transfer_message(data) try:
self.transfer_message(data)
except Exception as ex:
log.warn("Error occurred when sending message: %s.", ex)
log.exception(ex)
raise
def connectionMade(self): # NOQA def connectionMade(self): # NOQA
""" """
@ -215,7 +220,7 @@ class DelugeRPCProtocol(DelugeTransferProtocol):
"send(causing exception goes next):\n%s", formated_tb) "send(causing exception goes next):\n%s", formated_tb)
try: try:
raise WrappedException(str(exceptionValue), exceptionType.__name__, formated_tb) raise WrappedException(str(exceptionValue), exceptionType.__name__, formated_tb)
except Exception: except WrappedException:
send_error() send_error()
except Exception as ex: except Exception as ex:
log.error("An exception occurred while sending RPC_ERROR to client: %s", ex) log.error("An exception occurred while sending RPC_ERROR to client: %s", ex)
@ -245,7 +250,12 @@ class DelugeRPCProtocol(DelugeTransferProtocol):
if not ret: if not ret:
self.transport.loseConnection() self.transport.loseConnection()
return return
elif method == "daemon.set_event_interest" and self.valid_session():
# Anything below requires a valid session
if not self.valid_session():
return
if method == "daemon.set_event_interest":
log.debug("RPC dispatch daemon.set_event_interest") log.debug("RPC dispatch daemon.set_event_interest")
# This special case is to allow clients to set which events they are # This special case is to allow clients to set which events they are
# interested in receiving. # interested in receiving.
@ -260,44 +270,54 @@ class DelugeRPCProtocol(DelugeTransferProtocol):
self.sendData((RPC_RESPONSE, request_id, (True))) self.sendData((RPC_RESPONSE, request_id, (True)))
return return
if method in self.factory.methods and self.valid_session(): if method not in self.factory.methods:
log.debug("RPC dispatch %s", method)
try: try:
method_auth_requirement = self.factory.methods[method]._rpcserver_auth_level # Raise exception to be sent back to client
auth_level = self.factory.authorized_sessions[self.transport.sessionno][0] raise AttributeError("RPC call on invalid function '%s'." % method)
if auth_level < method_auth_requirement: except AttributeError:
# This session is not allowed to call this method
log.debug("Session %s is trying to call a method it is not "
"authorized to call!", self.transport.sessionno)
raise NotAuthorizedError(auth_level, method_auth_requirement)
# Set the session_id in the factory so that methods can know
# which session is calling it.
self.factory.session_id = self.transport.sessionno
ret = self.factory.methods[method](*args, **kwargs)
except Exception as ex:
send_error() send_error()
# Don't bother printing out DelugeErrors, because they are just return
# for the client
if not isinstance(ex, DelugeError): log.debug("RPC dispatch %s", method)
log.exception("Exception calling RPC request: %s", ex) try:
else: method_auth_requirement = self.factory.methods[method]._rpcserver_auth_level
# Check if the return value is a deferred, since we'll need to auth_level = self.factory.authorized_sessions[self.transport.sessionno][0]
# wait for it to fire before sending the RPC_RESPONSE if auth_level < method_auth_requirement:
if isinstance(ret, defer.Deferred): # This session is not allowed to call this method
def on_success(result): log.debug("Session %s is attempting an unauthorized method call!",
self.transport.sessionno)
raise NotAuthorizedError(auth_level, method_auth_requirement)
# Set the session_id in the factory so that methods can know
# which session is calling it.
self.factory.session_id = self.transport.sessionno
ret = self.factory.methods[method](*args, **kwargs)
except Exception as ex:
send_error()
# Don't bother printing out DelugeErrors, because they are just
# for the client
if not isinstance(ex, DelugeError):
log.exception("Exception calling RPC request: %s", ex)
else:
# Check if the return value is a deferred, since we'll need to
# wait for it to fire before sending the RPC_RESPONSE
if isinstance(ret, defer.Deferred):
def on_success(result):
try:
self.sendData((RPC_RESPONSE, request_id, result)) self.sendData((RPC_RESPONSE, request_id, result))
return result except Exception:
send_error()
return result
def on_fail(failure): def on_fail(failure):
try: try:
failure.raiseException() failure.raiseException()
except Exception: except Exception:
send_error() send_error()
return failure return failure
ret.addCallbacks(on_success, on_fail) ret.addCallbacks(on_success, on_fail)
else: else:
self.sendData((RPC_RESPONSE, request_id, ret)) self.sendData((RPC_RESPONSE, request_id, ret))
class RPCServer(component.Component): class RPCServer(component.Component):

View File

@ -144,6 +144,22 @@ class ClientTestCase(BaseTestCase):
d.addErrback(on_failure) d.addErrback(on_failure)
return d return d
@defer.inlineCallbacks
def test_invalid_rpc_method_call(self):
yield client.connect(
"localhost", self.listen_port, username="", password=""
)
d = client.core.invalid_method()
def on_failure(failure):
self.assertEqual(
failure.trap(error.WrappedException),
error.WrappedException
)
self.addCleanup(client.disconnect)
d.addErrback(on_failure)
yield d
def test_connect_without_sending_client_version_fails(self): def test_connect_without_sending_client_version_fails(self):
username, password = deluge.ui.common.get_localhost_auth() username, password = deluge.ui.common.get_localhost_auth()
no_version_sending_client = NoVersionSendingClient() no_version_sending_client = NoVersionSendingClient()

View File

@ -78,7 +78,7 @@ class RPCServerTestCase(BaseTestCase):
def test_client_login_error(self): def test_client_login_error(self):
# This test causes error log prints while running the test... # This test causes error log prints while running the test...
self.protocol.transport = None # This should causes AttributeError self.protocol.transport = None # This should cause AttributeError
self.authmanager = AuthManager() self.authmanager = AuthManager()
auth = get_localhost_auth() auth = get_localhost_auth()
self.protocol.dispatch(self.request_id, "daemon.login", auth, {"client_version": "Test"}) self.protocol.dispatch(self.request_id, "daemon.login", auth, {"client_version": "Test"})
@ -88,6 +88,16 @@ class RPCServerTestCase(BaseTestCase):
self.assertEquals(msg[2], "WrappedException") self.assertEquals(msg[2], "WrappedException")
self.assertEquals(msg[3][1], "AttributeError") self.assertEquals(msg[3][1], "AttributeError")
def test_client_invalid_method_call(self):
self.authmanager = AuthManager()
auth = get_localhost_auth()
self.protocol.dispatch(self.request_id, "invalid_function", auth, {})
msg = self.protocol.messages.pop()
self.assertEquals(msg[0], rpcserver.RPC_ERROR)
self.assertEquals(msg[1], self.request_id)
self.assertEquals(msg[2], "WrappedException")
self.assertEquals(msg[3][1], "AttributeError")
def test_daemon_info(self): def test_daemon_info(self):
self.protocol.dispatch(self.request_id, "daemon.info", [], {}) self.protocol.dispatch(self.request_id, "daemon.info", [], {})
msg = self.protocol.messages.pop() msg = self.protocol.messages.pop()