From b1e4b34b79fe5e4e8e6bc0b9d498fde372d28854 Mon Sep 17 00:00:00 2001 From: James Lu Date: Sat, 19 Nov 2016 17:00:18 -0800 Subject: [PATCH] Switch 'identify' to use the new login backend, add passlib to README dependencies This new backend supports optional encryption (sha256_crypt / sha512_crypt via passlib). Closes #322. --- README.md | 1 + coremods/corecommands.py | 31 +++++++++++-------------------- coremods/login.py | 32 +++++++++++++++++++++++++++----- 3 files changed, 39 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index fd98ec9..48123c2 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,7 @@ First, make sure the following dependencies are met: * Setuptools (`pip3 install setuptools`) * PyYAML (`pip3 install pyyaml`) * [ircmatch](https://github.com/mammon-ircd/ircmatch) (`pip3 install ircmatch`) +* passlib (`pip3 install passlib`) * *For the servprotect plugin*: [expiringdict](https://github.com/mailgun/expiringdict) (install this from source; installation is broken in pip due to [mailgun/expiringdict#13](https://github.com/mailgun/expiringdict/issues/13)) 1) Clone the repository: `git clone https://github.com/GLolol/PyLink && cd PyLink` diff --git a/coremods/corecommands.py b/coremods/corecommands.py index f39a63f..abe2a9e 100644 --- a/coremods/corecommands.py +++ b/coremods/corecommands.py @@ -9,7 +9,7 @@ import gc import sys import importlib -from . import control +from . import control, login from pylinkirc import utils, world, conf from pylinkirc.log import log @@ -43,27 +43,18 @@ def identify(irc, source, args): irc.reply('Error: Not enough arguments.') return - username = username.lower() + # Process new-style accounts. + if login.checkLogin(username, password): + _login(irc, source, username) + return - # Process new-style accounts. Note: usernames are case-insensitive, passwords are NOT. - for account, block in conf.conf['login'].get('accounts', {}).items(): - if account.lower() == username: - # Username matched config. - if password and password == block.get('password'): - # Password exists and matched config. TODO: hash user passwords in config. - _login(irc, source, account) - break - else: - _loginfail(irc, source, account) - break + # 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: - # 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) + # Username not found. + _loginfail(irc, source, username) @utils.add_cmd diff --git a/coremods/login.py b/coremods/login.py index 94b69d0..5b7280c 100644 --- a/coremods/login.py +++ b/coremods/login.py @@ -1,5 +1,5 @@ """ -login.py - Implement core login abstraction +login.py - Implement core login abstraction. """ from pylinkirc import conf, utils, world @@ -8,12 +8,33 @@ from passlib.apps import custom_app_context as pwd_context def checkLogin(user, password): """Checks whether the given user and password is a valid combination.""" - try: - passhash = conf.conf['login']['accounts'][user].get('password') - except KeyError: # Invalid combination + accounts = conf.conf['login'].get('accounts') + if not accounts: + # No accounts specified, return. return False - return verifyHash(password, passhash) + # 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: + account = accounts[user] + except KeyError: # Invalid combination + return False + else: + passhash = account.get('password') + if not passhash: + # No password given, return. XXX: we should allow plugins to override + # this in the future. + return False + + # Encryption in account passwords is optional (to not break backwards + # compatibility). + if account.get('encrypted', False): + return verifyHash(password, passhash) + else: + return password == passhash def verifyHash(password, passhash): """Checks whether the password given matches the hash.""" @@ -38,6 +59,7 @@ def mkpasswd(irc, source, args): return if not password: irc.error("Password cannot be empty.") + return hashed_pass = pwd_context.encrypt(password) irc.reply(hashed_pass, private=True)