diff --git a/deluge/core/authmanager.py b/deluge/core/authmanager.py index 106351b43..120481c3c 100644 --- a/deluge/core/authmanager.py +++ b/deluge/core/authmanager.py @@ -54,6 +54,9 @@ AUTH_LEVEL_DEFAULT = AUTH_LEVEL_NORMAL class BadLoginError(deluge.error.DelugeError): pass +class PasswordRequired(BadLoginError): + pass + class AuthManager(component.Component): def __init__(self): component.Component.__init__(self, "AuthManager") @@ -68,6 +71,16 @@ class AuthManager(component.Component): def shutdown(self): pass + def peek(self, username): + if username not in self.__auth: + # Let's try to re-load the file.. Maybe it's been updated + self.__load_auth_file() + if username not in self.__auth: + raise BadLoginError("Username does not exist") + + return int(self.__auth[username][1]) + + def authorize(self, username, password): """ Authorizes users based on username and password @@ -80,16 +93,12 @@ class AuthManager(component.Component): :raises BadLoginError: if the username does not exist or password does not match """ - - if username not in self.__auth: - # Let's try to re-load the file.. Maybe it's been updated - self.__load_auth_file() - if username not in self.__auth: - raise BadLoginError("Username does not exist") - + auth_level = self.peek(username) if self.__auth[username][0] == password: # Return the users auth level - return int(self.__auth[username][1]) + return auth_level + elif not password and self.__auth[username][0]: + raise PasswordRequired("Password is required") else: raise BadLoginError("Password does not match") diff --git a/deluge/core/rpcserver.py b/deluge/core/rpcserver.py index e309f7bda..9620c7b81 100644 --- a/deluge/core/rpcserver.py +++ b/deluge/core/rpcserver.py @@ -253,9 +253,25 @@ class DelugeRPCProtocol(Protocol): "".join(traceback.format_tb(exceptionTraceback))) )) - if method == "daemon.login": + if method == "daemon.peek": + try: + ret = component.get("AuthManager").peek(*args, **kwargs) + if ret: + self.factory.authorized_sessions[self.transport.sessionno] = (ret, args[0]) + self.factory.session_protocols[self.transport.sessionno] = self + except Exception, e: + sendError() + log.exception(e) + else: + self.sendData((RPC_RESPONSE, request_id, ret)) + if not ret: + self.transport.loseConnection() + finally: + return + elif method == "daemon.login": # This is a special case and used in the initial connection process # We need to authenticate the user here + log.debug("RPC dispatch daemon.login") try: ret = component.get("AuthManager").authorize(*args, **kwargs) if ret: @@ -271,6 +287,7 @@ class DelugeRPCProtocol(Protocol): finally: return elif method == "daemon.set_event_interest" and self.transport.sessionno in self.factory.authorized_sessions: + log.debug("RPC dispatch daemon.set_event_interest") # This special case is to allow clients to set which events they are # interested in receiving. # We are expecting a sequence from the client. @@ -286,6 +303,7 @@ class DelugeRPCProtocol(Protocol): return if method in self.factory.methods and self.transport.sessionno in self.factory.authorized_sessions: + log.debug("RPC dispatch %s", method) try: method_auth_requirement = self.factory.methods[method]._rpcserver_auth_level auth_level = self.factory.authorized_sessions[self.transport.sessionno][0] diff --git a/deluge/ui/client.py b/deluge/ui/client.py index 1a45e7d19..13a7be87c 100644 --- a/deluge/ui/client.py +++ b/deluge/ui/client.py @@ -261,6 +261,26 @@ class DaemonSSLProxy(DaemonProxy): self.disconnect_deferred = None self.disconnect_callback = None + def peek(self, host, port, username): + self.host = host + self.port = port + self.__connector = reactor.connectSSL(self.host, self.port, self.__factory, ssl.ClientContextFactory()) + self.connect_deferred = defer.Deferred() + self.peek_deferred = defer.Deferred() + + def on_connect(result, username): + self.__login_deferred = self.call("daemon.peek", username) + self.__login_deferred.addCallback(self.__on_peek, username) + self.__login_deferred.addErrback(self.__on_peek_fail) + + def on_connect_fail(reason): + log.debug("connect_fail: %s", reason) + self.peek_deferred.errback(reason) + + self.connect_deferred.addCallback(on_connect, username) + self.connect_deferred.addErrback(on_connect_fail) + return self.peek_deferred + def connect(self, host, port, username, password): """ Connects to a daemon at host:port @@ -273,6 +293,7 @@ class DaemonSSLProxy(DaemonProxy): :returns: twisted.Deferred """ + log.debug("sslproxy.connect()") self.host = host self.port = port self.__connector = reactor.connectSSL(self.host, self.port, self.__factory, ssl.ClientContextFactory()) @@ -286,6 +307,7 @@ class DaemonSSLProxy(DaemonProxy): return self.login_deferred def disconnect(self): + log.debug("sslproxy.disconnect()") self.disconnect_deferred = defer.Deferred() self.__connector.disconnect() return self.disconnect_deferred @@ -397,15 +419,18 @@ class DaemonSSLProxy(DaemonProxy): return error_data def __on_connect(self, result, username, password): + log.debug("__on_connect called") self.__login_deferred = self.call("daemon.login", username, password) self.__login_deferred.addCallback(self.__on_login, username) self.__login_deferred.addErrback(self.__on_login_fail) def __on_connect_fail(self, reason): + log.debug("__on_connect_fail called") log.debug("connect_fail: %s", reason) self.login_deferred.errback(reason) def __on_login(self, result, username): + log.debug("__on_login called") self.username = username # We need to tell the daemon what events we're interested in receiving if self.__factory.event_handlers: @@ -416,6 +441,15 @@ class DaemonSSLProxy(DaemonProxy): log.debug("_on_login_fail(): %s", result) self.login_deferred.errback(result) + def __on_peek(self, result, username): + log.debug("__on_peek called. result: %s", result) + self.username = username + self.peek_deferred.callback(result) + + def __on_peek_fail(self, result): + log.debug("__on_peek_fail called. result: %s", result) + self.peek_deferred.errback(result) + def set_disconnect_callback(self, cb): """ Set a function to be called when the connection to the daemon is lost @@ -531,6 +565,7 @@ class Client(object): :returns: a Deferred object that will be called once the connection has been established or fails """ + log.debug("real client connect") if not username and host in ("127.0.0.1", "localhost"): # No username was provided and it's the localhost, so we can try # to grab the credentials from the auth file. @@ -548,6 +583,24 @@ class Client(object): d.addErrback(on_connect_fail) return d + def peek(self, host="127.0.0.1", port=58846, username=""): + if not username and host in ("127.0.0.1", "localhost"): + # No username was provided and it's the localhost, so we can try + # to grab the credentials from the auth file. + import common + username, password = common.get_localhost_auth() + + self._daemon_proxy = DaemonSSLProxy(dict(self.__event_handlers)) + self._daemon_proxy.set_disconnect_callback(self.__on_disconnect) + d = self._daemon_proxy.peek(host, port, username) + def on_connect_fail(result): + log.debug("on_connect_fail: %s", result) + self.disconnect() + return result + + d.addErrback(on_connect_fail) + return d + def disconnect(self): """ Disconnects from the daemon. diff --git a/deluge/ui/gtkui/connectionmanager.py b/deluge/ui/gtkui/connectionmanager.py index 2ea302e76..5014a437a 100644 --- a/deluge/ui/gtkui/connectionmanager.py +++ b/deluge/ui/gtkui/connectionmanager.py @@ -133,6 +133,11 @@ class ConnectionManager(component.Component): self.glade.get_widget("image1").set_from_pixbuf(common.get_logo(32)) + self.askpassword_dialog = self.glade.get_widget("askpassword_dialog") + self.askpassword_dialog.set_transient_for(self.connection_manager) + self.askpassword_dialog.set_icon(common.get_deluge_icon()) + self.askpassword_dialog_entry = self.glade.get_widget("askpassword_dialog_entry") + self.hostlist = self.glade.get_widget("hostlist") # Create status pixbufs @@ -317,7 +322,7 @@ class ConnectionManager(component.Component): # Create a new Client instance c = deluge.ui.client.Client() - d = c.connect(host, port, user, password) + d = c.peek(host, port, user) d.addCallback(on_connect, c, host_id) d.addErrback(on_connect_failed, host_id) @@ -352,8 +357,11 @@ class ConnectionManager(component.Component): model, row = self.hostlist.get_selection().get_selected() if not row: + self.glade.get_widget("button_edithost").set_sensitive(False) return + self.glade.get_widget("button_edithost").set_sensitive(True) + # Get some values about the selected host status = model[row][HOSTLIST_COL_STATUS] host = model[row][HOSTLIST_COL_HOST] @@ -428,6 +436,7 @@ that you forgot to install the deluged package or it's not in your PATH.")).run( # Signal handlers def __on_connected(self, connector, host_id): + log.debug("__on_connected called") if self.gtkui_config["autoconnect"]: self.gtkui_config["autoconnect_host_id"] = host_id @@ -450,6 +459,10 @@ that you forgot to install the deluged package or it's not in your PATH.")).run( user = model[row][HOSTLIST_COL_USER] password = model[row][HOSTLIST_COL_PASS] + if not password: + self.askpassword_dialog.run() + password = self.askpassword_dialog_entry.get_text() + if status == _("Offline") and self.glade.get_widget("chk_autostart").get_active() and\ host in ("127.0.0.1", "localhost"): # We need to start this localhost @@ -475,7 +488,9 @@ that you forgot to install the deluged package or it's not in your PATH.")).run( def do_connect(*args): - client.connect(host, port, user, password).addCallback(self.__on_connected, host_id) + d = client.connect(host, port, user, password) + d.addCallback(self.__on_connected, host_id) + d.addErrback(self.__on_connected_failed, host_id, host, port, user) if client.connected(): client.disconnect().addCallback(do_connect) @@ -496,6 +511,10 @@ that you forgot to install the deluged package or it's not in your PATH.")).run( port_spinbutton = self.glade.get_widget("spinbutton_port") username_entry = self.glade.get_widget("entry_username") password_entry = self.glade.get_widget("entry_password") + button_addhost_save = self.glade.get_widget("button_addhost_save") + button_addhost_save.hide() + button_addhost_add = self.glade.get_widget("button_addhost_add") + button_addhost_add.show() response = dialog.run() if response == 1: username = username_entry.get_text() @@ -515,6 +534,54 @@ that you forgot to install the deluged package or it's not in your PATH.")).run( port_spinbutton.set_value(58846) dialog.hide() + def on_button_edithost_clicked(self, widget=None): + log.debug("on_button_edithost_clicked") + model, row = self.hostlist.get_selection().get_selected() + status = model[row][HOSTLIST_COL_STATUS] + if status == _("Connected"): + def on_disconnect(reason): + self.__update_list() + client.disconnect().addCallback(on_disconnect) + return + + dialog = self.glade.get_widget("addhost_dialog") + dialog.set_transient_for(self.connection_manager) + dialog.set_position(gtk.WIN_POS_CENTER_ON_PARENT) + hostname_entry = self.glade.get_widget("entry_hostname") + port_spinbutton = self.glade.get_widget("spinbutton_port") + username_entry = self.glade.get_widget("entry_username") + password_entry = self.glade.get_widget("entry_password") + button_addhost_save = self.glade.get_widget("button_addhost_save") + button_addhost_save.show() + button_addhost_add = self.glade.get_widget("button_addhost_add") + button_addhost_add.hide() + + username_entry.set_text(self.liststore[row][HOSTLIST_COL_USER]) + password_entry.set_text(self.liststore[row][HOSTLIST_COL_PASS]) + hostname_entry.set_text(self.liststore[row][HOSTLIST_COL_HOST]) + port_spinbutton.set_value(self.liststore[row][HOSTLIST_COL_PORT]) + + response = dialog.run() + + if response == 2: + self.liststore[row][HOSTLIST_COL_HOST] = hostname_entry.get_text() + self.liststore[row][HOSTLIST_COL_PORT] = port_spinbutton.get_value_as_int() + self.liststore[row][HOSTLIST_COL_USER] = username_entry.get_text() + self.liststore[row][HOSTLIST_COL_PASS] = password_entry.get_text() + self.liststore[row][HOSTLIST_COL_STATUS] = _("Offline") + + # Save the host list to file + self.__save_hostlist() + + # Update the status of the hosts + self.__update_list() + + username_entry.set_text("") + password_entry.set_text("") + hostname_entry.set_text("") + port_spinbutton.set_value(58846) + dialog.hide() + def on_button_removehost_clicked(self, widget): log.debug("on_button_removehost_clicked") # Get the selected rows @@ -579,3 +646,17 @@ that you forgot to install the deluged package or it's not in your PATH.")).run( def on_hostlist_selection_changed(self, treeselection): self.__update_buttons() + + def on_askpassword_dialog_connect_button_clicked(self, widget): + log.debug("on on_askpassword_dialog_connect_button_clicked") + self.askpassword_dialog.response(gtk.RESPONSE_OK) + + def on_askpassword_dialog_entry_activate(self, entry): + self.askpassword_dialog.response(gtk.RESPONSE_OK) + + def __on_connected_failed(self, reason, host_id, host, port, user): + log.exception(reason) + log.debug(reason.value) + log.debug(reason.value.__dict__) + dialogs.ErrorDialog(_("Failed To Authenticate"), + reason.value.exception_msg).run() diff --git a/deluge/ui/gtkui/glade/connection_manager.glade b/deluge/ui/gtkui/glade/connection_manager.glade index af4129507..fa93556dc 100644 --- a/deluge/ui/gtkui/glade/connection_manager.glade +++ b/deluge/ui/gtkui/glade/connection_manager.glade @@ -1,15 +1,15 @@ - - - + + + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 5 Add Host True - GTK_WIN_POS_CENTER + center True - GDK_WINDOW_TYPE_HINT_DIALOG + dialog False @@ -30,6 +30,7 @@ False False + 0 @@ -159,38 +160,59 @@ True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_BUTTONBOX_END + end + gtk-cancel True True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - gtk-cancel True - 0 + + False + False + 0 + + gtk-add + 1 True True True True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - gtk-add True - 1 + False + False 1 + + + gtk-save + 2 + True + True + True + + + False + False + 2 + + False - GTK_PACK_END + end + 0 @@ -203,11 +225,11 @@ 5 Connection Manager True - GTK_WIN_POS_CENTER_ON_PARENT + center-on-parent 350 300 True - GDK_WINDOW_TYPE_HINT_DIALOG + dialog False @@ -228,6 +250,7 @@ False False + 0 @@ -258,14 +281,14 @@ True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_RESIZE_QUEUE + queue True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_POLICY_AUTOMATIC - GTK_POLICY_AUTOMATIC + automatic + automatic True @@ -277,6 +300,9 @@ + + 0 + @@ -286,48 +312,69 @@ True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_BUTTONBOX_START + start + gtk-add True True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - gtk-add True - 0 + + False + False + 0 + + + + + gtk-edit + True + False + True + True + True + + + + False + False + 1 + + gtk-remove True True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - gtk-remove True - 0 - 1 + False + False + 2 False False + 0 + gtk-refresh True True True - gtk-refresh True - 0 @@ -342,7 +389,6 @@ True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 @@ -355,6 +401,9 @@ GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK gtk-execute + + 0 + @@ -373,7 +422,7 @@ False False - GTK_PACK_END + end 1 @@ -409,22 +458,25 @@ GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Automatically connect to selected host on start-up True True + False GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Automatically connect to selected host on start-up - 0 True + + 0 + + Automatically start localhost if needed True True + False GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Automatically start localhost if needed - 0 True @@ -434,11 +486,11 @@ + Do not show this dialog on start-up True True + False GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Do not show this dialog on start-up - 0 True @@ -469,31 +521,30 @@ True - GTK_BUTTONBOX_END + end + gtk-close True True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - gtk-close True - -7 False False + 0 + gtk-connect True True True - gtk-connect True - 0 @@ -513,12 +564,87 @@ False GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_BUTTONBOX_END + end False False - GTK_PACK_END + end + 0 + + + + + + + 5 + Password Required + True + center-on-parent + 320 + True + dialog + True + False + + + True + 2 + + + True + + + True + gtk-dialog-authentication + 6 + + + 0 + + + + + True + True + False + + + + + 1 + + + + + 1 + + + + + True + end + + + gtk-connect + 1 + True + True + True + True + + + + False + False + 0 + + + + + False + end + 0