mirror of
https://github.com/jlu5/PyLink.git
synced 2024-11-01 09:19:23 +01:00
Rewrite login handling (Closes #590)
* Move identify command and login helpers into coremods.login - corecommands._login -> login._irc_try_login * Add login._get_account() function to consistently fetch login block info * Rename functions in coremods.login to snake case: - checkLogin -> check_login - verifyHash -> verify_hash * Replace explicit returns in login checks with raising utils.NotAuthorizedError()
This commit is contained in:
parent
73261a31bd
commit
9e936f1612
@ -13,74 +13,6 @@ from pylinkirc.log import log
|
|||||||
# Essential, core commands go here so that the "commands" plugin with less-important,
|
# Essential, core commands go here so that the "commands" plugin with less-important,
|
||||||
# but still generic functions can be reloaded.
|
# but still generic functions can be reloaded.
|
||||||
|
|
||||||
def _login(irc, source, username):
|
|
||||||
"""Internal function to process logins."""
|
|
||||||
# Mangle case before we start checking for login data.
|
|
||||||
accounts = {k.lower(): v for k, v in conf.conf['login'].get('accounts', {}).items()}
|
|
||||||
|
|
||||||
if irc.is_internal_client(source):
|
|
||||||
irc.error("Cannot use 'identify' via a command proxy.")
|
|
||||||
return
|
|
||||||
|
|
||||||
logindata = accounts.get(username.lower(), {})
|
|
||||||
|
|
||||||
network_filter = logindata.get('networks')
|
|
||||||
require_oper = logindata.get('require_oper', False)
|
|
||||||
hosts_filter = logindata.get('hosts', [])
|
|
||||||
|
|
||||||
if network_filter and irc.name not in network_filter:
|
|
||||||
irc.error("You are not authorized to log in to %r on this network." % username)
|
|
||||||
log.warning("(%s) Failed login to %r from %s (wrong network: networks filter says %r but we got %r)", irc.name, username, irc.get_hostmask(source), ', '.join(network_filter), irc.name)
|
|
||||||
return
|
|
||||||
|
|
||||||
elif require_oper and not irc.is_oper(source, allowAuthed=False):
|
|
||||||
irc.error("You must be opered to log in to %r." % username)
|
|
||||||
log.warning("(%s) Failed login to %r from %s (needs oper)", irc.name, username, irc.get_hostmask(source))
|
|
||||||
return
|
|
||||||
|
|
||||||
elif hosts_filter and not any(irc.match_host(host, source) for host in hosts_filter):
|
|
||||||
irc.error("Failed to log in to %r: hostname mismatch." % username)
|
|
||||||
log.warning("(%s) Failed login to %r from %s (hostname mismatch)", irc.name, username, irc.get_hostmask(source))
|
|
||||||
return
|
|
||||||
|
|
||||||
irc.users[source].account = username
|
|
||||||
irc.reply('Successfully logged in as %s.' % username)
|
|
||||||
log.info("(%s) Successful login to %r by %s",
|
|
||||||
irc.name, username, irc.get_hostmask(source))
|
|
||||||
|
|
||||||
def _loginfail(irc, source, username):
|
|
||||||
"""Internal function to process login failures."""
|
|
||||||
irc.error('Incorrect credentials.')
|
|
||||||
log.warning("(%s) Failed login to %r from %s", irc.name, username, irc.get_hostmask(source))
|
|
||||||
|
|
||||||
def identify(irc, source, args):
|
|
||||||
"""<username> <password>
|
|
||||||
|
|
||||||
Logs in to PyLink using the configured administrator account."""
|
|
||||||
if irc.is_channel(irc.called_in):
|
|
||||||
irc.reply('Error: This command must be sent in private. '
|
|
||||||
'(Would you really type a password inside a channel?)')
|
|
||||||
return
|
|
||||||
try:
|
|
||||||
username, password = args[0], args[1]
|
|
||||||
except IndexError:
|
|
||||||
irc.reply('Error: Not enough arguments.')
|
|
||||||
return
|
|
||||||
|
|
||||||
# Process new-style accounts.
|
|
||||||
if login.checkLogin(username, password):
|
|
||||||
_login(irc, source, username)
|
|
||||||
return
|
|
||||||
|
|
||||||
# Process legacy logins (login:user).
|
|
||||||
if username.lower() == conf.conf['login'].get('user', '').lower() and password == conf.conf['login'].get('password'):
|
|
||||||
realuser = conf.conf['login']['user']
|
|
||||||
_login(irc, source, realuser)
|
|
||||||
else:
|
|
||||||
# Username not found.
|
|
||||||
_loginfail(irc, source, username)
|
|
||||||
utils.add_cmd(identify, aliases=('login', 'id'))
|
|
||||||
|
|
||||||
@utils.add_cmd
|
@utils.add_cmd
|
||||||
def shutdown(irc, source, args):
|
def shutdown(irc, source, args):
|
||||||
"""takes no arguments.
|
"""takes no arguments.
|
||||||
|
@ -18,37 +18,39 @@ if CryptContext:
|
|||||||
sha256_crypt__default_rounds=180000,
|
sha256_crypt__default_rounds=180000,
|
||||||
sha512_crypt__default_rounds=90000)
|
sha512_crypt__default_rounds=90000)
|
||||||
|
|
||||||
def checkLogin(user, password):
|
def _get_account(accountname):
|
||||||
"""Checks whether the given user and password is a valid combination."""
|
"""
|
||||||
accounts = conf.conf['login'].get('accounts')
|
Returns the login data block for the given account name (case-insensitive), or False if none
|
||||||
if not accounts:
|
exists.
|
||||||
# No accounts specified, return.
|
"""
|
||||||
return False
|
accounts = {k.lower(): v for k, v in
|
||||||
|
conf.conf['login'].get('accounts', {}).items()}
|
||||||
# Lowercase account names to make them case insensitive. TODO: check for
|
|
||||||
# duplicates.
|
|
||||||
user = user.lower()
|
|
||||||
accounts = {k.lower(): v for k, v in accounts.items()}
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
account = accounts[user]
|
return accounts[accountname.lower()]
|
||||||
except KeyError: # Invalid combination
|
except KeyError:
|
||||||
return False
|
return False
|
||||||
else:
|
|
||||||
|
def check_login(user, password):
|
||||||
|
"""Checks whether the given user and password is a valid combination."""
|
||||||
|
account = _get_account(user)
|
||||||
|
|
||||||
|
if account:
|
||||||
passhash = account.get('password')
|
passhash = account.get('password')
|
||||||
if not passhash:
|
if not passhash:
|
||||||
# No password given, return. XXX: we should allow plugins to override
|
# No password given, return. XXX: we should allow plugins to override
|
||||||
# this in the future.
|
# this in the future.
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Encryption in account passwords is optional (to not break backwards
|
# Hashing in account passwords is optional.
|
||||||
# compatibility).
|
|
||||||
if account.get('encrypted', False):
|
if account.get('encrypted', False):
|
||||||
return verifyHash(password, passhash)
|
return verify_hash(password, passhash)
|
||||||
else:
|
else:
|
||||||
return password == passhash
|
return password == passhash
|
||||||
|
|
||||||
def verifyHash(password, passhash):
|
return False
|
||||||
|
|
||||||
|
def verify_hash(password, passhash):
|
||||||
"""Checks whether the password given matches the hash."""
|
"""Checks whether the password given matches the hash."""
|
||||||
if password:
|
if password:
|
||||||
if not pwd_context:
|
if not pwd_context:
|
||||||
@ -57,3 +59,65 @@ def verifyHash(password, passhash):
|
|||||||
|
|
||||||
return pwd_context.verify(password, passhash)
|
return pwd_context.verify(password, passhash)
|
||||||
return False # No password given!
|
return False # No password given!
|
||||||
|
|
||||||
|
def _irc_try_login(irc, source, username):
|
||||||
|
"""Internal function to process logins via IRC."""
|
||||||
|
if irc.is_internal_client(source):
|
||||||
|
irc.error("Cannot use 'identify' via a command proxy.")
|
||||||
|
return
|
||||||
|
|
||||||
|
logindata = _get_account(username)
|
||||||
|
|
||||||
|
network_filter = logindata.get('networks')
|
||||||
|
require_oper = logindata.get('require_oper', False)
|
||||||
|
hosts_filter = logindata.get('hosts', [])
|
||||||
|
|
||||||
|
if network_filter and irc.name not in network_filter:
|
||||||
|
log.warning("(%s) Failed login to %r from %s (wrong network: networks filter says %r but we got %r)",
|
||||||
|
irc.name, username, irc.get_hostmask(source), ', '.join(network_filter), irc.name)
|
||||||
|
raise utils.NotAuthorizedError("Account is not authorized to login on this network.")
|
||||||
|
|
||||||
|
elif require_oper and not irc.is_oper(source, allowAuthed=False):
|
||||||
|
log.warning("(%s) Failed login to %r from %s (needs oper)", irc.name, username, irc.get_hostmask(source))
|
||||||
|
raise utils.NotAuthorizedError("You must be opered.")
|
||||||
|
|
||||||
|
elif hosts_filter and not any(irc.match_host(host, source) for host in hosts_filter):
|
||||||
|
log.warning("(%s) Failed login to %r from %s (hostname mismatch)", irc.name, username, irc.get_hostmask(source))
|
||||||
|
raise utils.NotAuthorizedError("Hostname mismatch.")
|
||||||
|
|
||||||
|
irc.users[source].account = username
|
||||||
|
irc.reply('Successfully logged in as %s.' % username)
|
||||||
|
log.info("(%s) Successful login to %r by %s",
|
||||||
|
irc.name, username, irc.get_hostmask(source))
|
||||||
|
return True
|
||||||
|
|
||||||
|
def identify(irc, source, args):
|
||||||
|
"""<username> <password>
|
||||||
|
|
||||||
|
Logs in to PyLink using the configured administrator account."""
|
||||||
|
if irc.is_channel(irc.called_in):
|
||||||
|
irc.reply('Error: This command must be sent in private. '
|
||||||
|
'(Would you really type a password inside a channel?)')
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
username, password = args[0], args[1]
|
||||||
|
except IndexError:
|
||||||
|
irc.reply('Error: Not enough arguments.')
|
||||||
|
return
|
||||||
|
|
||||||
|
# Process new-style accounts.
|
||||||
|
if check_login(username, password):
|
||||||
|
_irc_try_login(irc, source, username)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Process legacy logins (login:user).
|
||||||
|
if username.lower() == conf.conf['login'].get('user', '').lower() and password == conf.conf['login'].get('password'):
|
||||||
|
realuser = conf.conf['login']['user']
|
||||||
|
_irc_try_login(irc, source, realuser)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Username not found or password incorrect.
|
||||||
|
log.warning("(%s) Failed login to %r from %s", irc.name, username, irc.get_hostmask(source))
|
||||||
|
raise utils.NotAuthorizedError('Bad username or password.')
|
||||||
|
|
||||||
|
utils.add_cmd(identify, aliases=('login', 'id'))
|
||||||
|
Loading…
Reference in New Issue
Block a user