Some account management work. Not yet complete.

This commit is contained in:
Pedro Algarvio 2011-04-22 18:51:51 +01:00
parent 342da12d0c
commit 8195421c99
4 changed files with 988 additions and 356 deletions

View File

@ -51,10 +51,29 @@ AUTH_LEVEL_ADMIN = 10
AUTH_LEVEL_DEFAULT = AUTH_LEVEL_NORMAL AUTH_LEVEL_DEFAULT = AUTH_LEVEL_NORMAL
class Account(object):
__slots__ = ('username', 'password', 'auth_level')
def __init__(self, username, password, auth_level):
self.username = username
self.password = password
self.auth_level = auth_level
def data(self, include_private=True):
rv = self.__dict__.copy()
if not include_private:
rv['password'] = ''
return rv
def __repr__(self):
return ('<Account username="%(username)s" auth_level=%(auth_level)s>' %
self.__dict__)
class AuthManager(component.Component): class AuthManager(component.Component):
def __init__(self): def __init__(self):
component.Component.__init__(self, "AuthManager") component.Component.__init__(self, "AuthManager")
self.__auth = {} self.__auth = {}
self.__auth_modification_time = None
def start(self): def start(self):
self.__load_auth_file() self.__load_auth_file()
@ -74,8 +93,10 @@ class AuthManager(component.Component):
:returns: int, the auth level for this user :returns: int, the auth level for this user
:rtype: int :rtype: int
:raises AuthenticationRequired: if aditional details are required to authenticate :raises AuthenticationRequired: if aditional details are required to
:raises BadLoginError: if the username does not exist or password does not match authenticate.
:raises BadLoginError: if the username does not exist or password does
not match.
""" """
if not username: if not username:
@ -85,21 +106,60 @@ class AuthManager(component.Component):
self.__test_existing_account(username) self.__test_existing_account(username)
if self.__auth[username][0] == password: if self.__auth[username].password == password:
# Return the users auth level # Return the users auth level
return int(self.__auth[username][1]) return self.__auth[username].auth_level
elif not password and self.__auth[username][0]: elif not password and self.__auth[username].password:
raise AuthenticationRequired("Password is required", username) raise AuthenticationRequired("Password is required", username)
else: else:
raise BadLoginError("Password does not match") raise BadLoginError("Password does not match")
def get_known_accounts(self): def get_known_accounts(self, include_private_data=False):
""" """
Returns a list of known deluge usernames. Returns a list of known deluge usernames.
""" """
self.__load_auth_file() self.__load_auth_file()
return self.__auth.keys() rv = {}
for account in self.__auth.items():
rv[account.username] = account.data(include_private_data)
return rv
def create_account(self, username, password='', auth_level=AUTH_LEVEL_DEFAULT):
if username in self.__auth:
raise Something()
self.__create_account(username, password, auth_level)
def update_account(self, username, password='', auth_level=AUTH_LEVEL_DEFAULT):
if username in self.__auth:
raise Something()
self.__create_account(username, password, auth_level)
def remove_account(self, username):
if username in self.__auth:
raise Something()
del self.__auth[username]
self.write_auth_file()
if component.get("RPCServer").get_session_user() == username:
# Force a client logout by the server
component.get("RPCServer").logout_current_session()
def write_auth_file(self):
old_auth_file = configmanager.get_config_dir("auth")
new_auth_file = old_auth_file + '.new'
fd = open(new_auth_file, "w")
for account in self.__auth.items():
fd.write(
"%(username)s:%(password)s:%(auth_level)s\n" % account.__dict__
)
fd.flush()
os.fsync(fd.fileno())
fd.close()
os.rename(new_auth_file, old_auth_file)
self.__load_auth_file()
def __add_account(self, username, password, auth_level):
self.__auth[username] = Account(username, password, auth_level)
self.write_auth_file()
def __test_existing_account(self, username): def __test_existing_account(self, username):
if username not in self.__auth: if username not in self.__auth:
@ -107,6 +167,7 @@ class AuthManager(component.Component):
self.__load_auth_file() self.__load_auth_file()
if username not in self.__auth: if username not in self.__auth:
raise BadLoginError("Username does not exist") raise BadLoginError("Username does not exist")
return True
def __create_localclient_account(self): def __create_localclient_account(self):
""" """
@ -117,24 +178,28 @@ class AuthManager(component.Component):
from hashlib import sha1 as sha_hash from hashlib import sha1 as sha_hash
except ImportError: except ImportError:
from sha import new as sha_hash from sha import new as sha_hash
return ("localclient:" + sha_hash(str(random.random())).hexdigest() + self.__auth["localclient"] = Account(
":" + str(AUTH_LEVEL_ADMIN) + "\n") "localclient",
sha_hash(str(random.random())).hexdigest(),
AUTH_LEVEL_ADMIN
)
def __load_auth_file(self): def __load_auth_file(self):
auth_file = configmanager.get_config_dir("auth") auth_file = configmanager.get_config_dir("auth")
# Check for auth file and create if necessary # Check for auth file and create if necessary
if not os.path.exists(auth_file): if not os.path.exists(auth_file):
localclient = self.__create_localclient_account() self.__create_auth_file()
fd = open(auth_file, "w") self.__create_localclient_account()
fd.write(localclient) self.write_auth_file()
fd.flush()
os.fsync(fd.fileno()) auth_file_modification_time = os.stat(auth_file).st_mtime
fd.close() if self.__auth_modification_time is None:
# Change the permissions on the file so only this user can read/write it self.__auth_modification_time = auth_file_modification_time
os.chmod(auth_file, stat.S_IREAD | stat.S_IWRITE) elif self.__auth_modification_time == auth_file_modification_time:
f = [localclient] # File didn't change, no need for re-parsing's
else: return
# Load the auth file into a dictionary: {username: password, ...}
# Load the auth file into a dictionary: {username: Account(...)}
f = open(auth_file, "r").readlines() f = open(auth_file, "r").readlines()
for line in f: for line in f:
@ -152,14 +217,36 @@ class AuthManager(component.Component):
log.warning("Your auth entry for %s contains no auth level, " log.warning("Your auth entry for %s contains no auth level, "
"using AUTH_LEVEL_DEFAULT(%s)..", username, "using AUTH_LEVEL_DEFAULT(%s)..", username,
AUTH_LEVEL_DEFAULT) AUTH_LEVEL_DEFAULT)
level = AUTH_LEVEL_DEFAULT auth_level = AUTH_LEVEL_DEFAULT
elif len(lsplit) == 3: elif len(lsplit) == 3:
username, password, level = lsplit username, password, auth_level = lsplit
else: else:
log.error("Your auth file is malformed: Incorrect number of fields!") log.error("Your auth file is malformed: Incorrect number of fields!")
continue continue
self.__auth[username.strip()] = (password.strip(), level) username = username.strip()
password = password.strip()
try:
auth_level = int(auth_level)
except ValueError:
log.error("Your auth file is malformed: %r is not a valid auth "
"level" % auth_level)
continue
self.__auth[username] = Account(username, password, auth_level)
if "localclient" not in self.__auth: if "localclient" not in self.__auth:
open(auth_file, "a").write(self.__create_localclient_account()) self.__create_localclient_account()
self.write_auth_file()
def __create_auth_file(self):
auth_file = configmanager.get_config_dir("auth")
# Check for auth file and create if necessary
if not os.path.exists(auth_file):
fd = open(auth_file, "w")
fd.flush()
os.fsync(fd.fileno())
fd.close()
# Change the permissions on the file so only this user can read/write it
os.chmod(auth_file, stat.S_IREAD | stat.S_IWRITE)

View File

@ -55,7 +55,7 @@ import deluge.common
import deluge.component as component import deluge.component as component
from deluge.event import * from deluge.event import *
from deluge.error import * from deluge.error import *
from deluge.core.authmanager import AUTH_LEVEL_ADMIN from deluge.core.authmanager import AUTH_LEVEL_ADMIN, AUTH_LEVEL_DEFAULT
from deluge.core.torrentmanager import TorrentManager from deluge.core.torrentmanager import TorrentManager
from deluge.core.pluginmanager import PluginManager from deluge.core.pluginmanager import PluginManager
from deluge.core.alertmanager import AlertManager from deluge.core.alertmanager import AlertManager
@ -829,6 +829,9 @@ class Core(component.Component):
""" """
return lt.version return lt.version
@export(AUTH_LEVEL_ADMIN) @export(AUTH_LEVEL_DEFAULT)
def get_known_accounts(self): def get_known_accounts(self):
return self.authmanager.get_known_accounts() auth_level = component.get("RPCServer").get_session_auth_level()
return self.authmanager.get_known_accounts(
include_private_data=(auth_level==AUTH_LEVEL_ADMIN)
)

View File

@ -485,6 +485,12 @@ class RPCServer(component.Component):
""" """
return session_id in self.factory.authorized_sessions return session_id in self.factory.authorized_sessions
def logout_current_session(self):
"""
Makes the current session invalid logging out the current account
"""
self.factory.protocol.connectionLost("Server logged out client")
def emit_event(self, event): def emit_event(self, event):
""" """
Emits the event to interested clients. Emits the event to interested clients.

File diff suppressed because it is too large Load Diff