[Core] Raise AttributeError on RPC call to invalid function
Also catch and log errors in rcpserver.sendData
This commit is contained in:
parent
382a99ad61
commit
a987c3ed39
|
@ -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):
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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()
|
||||||
|
|
Loading…
Reference in New Issue