changeset 8732:710512deb83d

lib: move some auth low level helper functions to utils2
author Mads Kiilerich <mads@kiilerich.com>
date Sun, 01 Nov 2020 23:50:29 +0100
parents fa0a18dc0fd5
children 8f5bc94959de
files kallithea/lib/auth.py kallithea/lib/auth_modules/__init__.py kallithea/lib/auth_modules/auth_internal.py kallithea/lib/utils2.py kallithea/model/user.py kallithea/tests/functional/test_admin_users.py kallithea/tests/functional/test_login.py kallithea/tests/scripts/manual_test_concurrency.py
diffstat 8 files changed, 80 insertions(+), 85 deletions(-) [+]
line wrap: on
line diff
--- a/kallithea/lib/auth.py	Sun Nov 01 23:54:39 2020 +0100
+++ b/kallithea/lib/auth.py	Sun Nov 01 23:50:29 2020 +0100
@@ -24,13 +24,9 @@
 :copyright: (c) 2013 RhodeCode GmbH, and others.
 :license: GPLv3, see LICENSE.md for more details.
 """
-import hashlib
 import itertools
 import logging
-import os
-import string
 
-import bcrypt
 import ipaddr
 from decorator import decorator
 from sqlalchemy.orm import joinedload
@@ -42,7 +38,6 @@
 import kallithea
 from kallithea.lib import webutils
 from kallithea.lib.utils import get_repo_group_slug, get_repo_slug, get_user_group_slug
-from kallithea.lib.utils2 import ascii_bytes, ascii_str, safe_bytes
 from kallithea.lib.vcs.utils.lazy import LazyProperty
 from kallithea.lib.webutils import url
 from kallithea.model import db, meta
@@ -52,70 +47,6 @@
 log = logging.getLogger(__name__)
 
 
-class PasswordGenerator(object):
-    """
-    This is a simple class for generating password from different sets of
-    characters
-    usage::
-
-        passwd_gen = PasswordGenerator()
-        #print 8-letter password containing only big and small letters
-            of alphabet
-        passwd_gen.gen_password(8, passwd_gen.ALPHABETS_BIG_SMALL)
-    """
-    ALPHABETS_NUM = r'''1234567890'''
-    ALPHABETS_SMALL = r'''qwertyuiopasdfghjklzxcvbnm'''
-    ALPHABETS_BIG = r'''QWERTYUIOPASDFGHJKLZXCVBNM'''
-    ALPHABETS_SPECIAL = r'''`-=[]\;',./~!@#$%^&*()_+{}|:"<>?'''
-    ALPHABETS_FULL = ALPHABETS_BIG + ALPHABETS_SMALL \
-        + ALPHABETS_NUM + ALPHABETS_SPECIAL
-    ALPHABETS_ALPHANUM = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM
-    ALPHABETS_BIG_SMALL = ALPHABETS_BIG + ALPHABETS_SMALL
-    ALPHABETS_ALPHANUM_BIG = ALPHABETS_BIG + ALPHABETS_NUM
-    ALPHABETS_ALPHANUM_SMALL = ALPHABETS_SMALL + ALPHABETS_NUM
-
-    def gen_password(self, length, alphabet=ALPHABETS_FULL):
-        assert len(alphabet) <= 256, alphabet
-        l = []
-        while len(l) < length:
-            i = ord(os.urandom(1))
-            if i < len(alphabet):
-                l.append(alphabet[i])
-        return ''.join(l)
-
-
-def get_crypt_password(password):
-    """
-    Cryptographic function used for bcrypt password hashing.
-
-    :param password: password to hash
-    """
-    return ascii_str(bcrypt.hashpw(safe_bytes(password), bcrypt.gensalt(10)))
-
-
-def check_password(password, hashed):
-    """
-    Checks password match the hashed value using bcrypt.
-    Remains backwards compatible and accept plain sha256 hashes which used to
-    be used on Windows.
-
-    :param password: password
-    :param hashed: password in hashed form
-    """
-    # sha256 hashes will always be 64 hex chars
-    # bcrypt hashes will always contain $ (and be shorter)
-    if len(hashed) == 64 and all(x in string.hexdigits for x in hashed):
-        return hashlib.sha256(password).hexdigest() == hashed
-    try:
-        return bcrypt.checkpw(safe_bytes(password), ascii_bytes(hashed))
-    except ValueError as e:
-        # bcrypt will throw ValueError 'Invalid hashed_password salt' on all password errors
-        log.error('error from bcrypt checking password: %s', e)
-        return False
-    log.error('check_password failed - no method found for hash length %s', len(hashed))
-    return False
-
-
 PERM_WEIGHTS = db.Permission.PERM_WEIGHTS
 
 def bump_permission(permissions, key, new_perm):
--- a/kallithea/lib/auth_modules/__init__.py	Sun Nov 01 23:54:39 2020 +0100
+++ b/kallithea/lib/auth_modules/__init__.py	Sun Nov 01 23:50:29 2020 +0100
@@ -20,9 +20,9 @@
 import traceback
 from inspect import isfunction
 
-from kallithea.lib.auth import AuthUser, PasswordGenerator
+from kallithea.lib.auth import AuthUser
 from kallithea.lib.compat import hybrid_property
-from kallithea.lib.utils2 import asbool
+from kallithea.lib.utils2 import asbool, PasswordGenerator
 from kallithea.model import db, meta, validators
 from kallithea.model.user import UserModel
 from kallithea.model.user_group import UserGroupModel
--- a/kallithea/lib/auth_modules/auth_internal.py	Sun Nov 01 23:54:39 2020 +0100
+++ b/kallithea/lib/auth_modules/auth_internal.py	Sun Nov 01 23:50:29 2020 +0100
@@ -28,7 +28,7 @@
 
 import logging
 
-from kallithea.lib import auth, auth_modules
+from kallithea.lib import auth_modules, utils2
 from kallithea.lib.compat import hybrid_property
 
 
@@ -78,7 +78,7 @@
         }
         log.debug('user data: %s', user_data)
 
-        password_match = auth.check_password(password, userobj.password)
+        password_match = utils2.check_password(password, userobj.password)
         if userobj.is_default_user:
             log.info('user %s authenticated correctly as anonymous user',
                      username)
--- a/kallithea/lib/utils2.py	Sun Nov 01 23:54:39 2020 +0100
+++ b/kallithea/lib/utils2.py	Sun Nov 01 23:50:29 2020 +0100
@@ -29,12 +29,16 @@
 
 import binascii
 import datetime
+import hashlib
 import json
+import logging
 import os
 import re
+import string
 import time
 import urllib.parse
 
+import bcrypt
 import urlobject
 from dateutil import relativedelta
 from sqlalchemy.engine import url as sa_url
@@ -59,6 +63,9 @@
     pass
 
 
+log = logging.getLogger(__name__)
+
+
 # mute pyflakes "imported but unused"
 assert asbool
 assert aslist
@@ -549,3 +556,67 @@
         if retries < 0:
             raise IOError
         print(complaint)
+
+
+class PasswordGenerator(object):
+    """
+    This is a simple class for generating password from different sets of
+    characters
+    usage::
+
+        passwd_gen = PasswordGenerator()
+        #print 8-letter password containing only big and small letters
+            of alphabet
+        passwd_gen.gen_password(8, passwd_gen.ALPHABETS_BIG_SMALL)
+    """
+    ALPHABETS_NUM = r'''1234567890'''
+    ALPHABETS_SMALL = r'''qwertyuiopasdfghjklzxcvbnm'''
+    ALPHABETS_BIG = r'''QWERTYUIOPASDFGHJKLZXCVBNM'''
+    ALPHABETS_SPECIAL = r'''`-=[]\;',./~!@#$%^&*()_+{}|:"<>?'''
+    ALPHABETS_FULL = ALPHABETS_BIG + ALPHABETS_SMALL \
+        + ALPHABETS_NUM + ALPHABETS_SPECIAL
+    ALPHABETS_ALPHANUM = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM
+    ALPHABETS_BIG_SMALL = ALPHABETS_BIG + ALPHABETS_SMALL
+    ALPHABETS_ALPHANUM_BIG = ALPHABETS_BIG + ALPHABETS_NUM
+    ALPHABETS_ALPHANUM_SMALL = ALPHABETS_SMALL + ALPHABETS_NUM
+
+    def gen_password(self, length, alphabet=ALPHABETS_FULL):
+        assert len(alphabet) <= 256, alphabet
+        l = []
+        while len(l) < length:
+            i = ord(os.urandom(1))
+            if i < len(alphabet):
+                l.append(alphabet[i])
+        return ''.join(l)
+
+
+def get_crypt_password(password):
+    """
+    Cryptographic function used for bcrypt password hashing.
+
+    :param password: password to hash
+    """
+    return ascii_str(bcrypt.hashpw(safe_bytes(password), bcrypt.gensalt(10)))
+
+
+def check_password(password, hashed):
+    """
+    Checks password match the hashed value using bcrypt.
+    Remains backwards compatible and accept plain sha256 hashes which used to
+    be used on Windows.
+
+    :param password: password
+    :param hashed: password in hashed form
+    """
+    # sha256 hashes will always be 64 hex chars
+    # bcrypt hashes will always contain $ (and be shorter)
+    if len(hashed) == 64 and all(x in string.hexdigits for x in hashed):
+        return hashlib.sha256(password).hexdigest() == hashed
+    try:
+        return bcrypt.checkpw(safe_bytes(password), ascii_bytes(hashed))
+    except ValueError as e:
+        # bcrypt will throw ValueError 'Invalid hashed_password salt' on all password errors
+        log.error('error from bcrypt checking password: %s', e)
+        return False
+    log.error('check_password failed - no method found for hash length %s', len(hashed))
+    return False
--- a/kallithea/model/user.py	Sun Nov 01 23:54:39 2020 +0100
+++ b/kallithea/model/user.py	Sun Nov 01 23:50:29 2020 +0100
@@ -38,7 +38,7 @@
 
 from kallithea.lib import webutils
 from kallithea.lib.exceptions import DefaultUserException, UserOwnsReposException
-from kallithea.lib.utils2 import generate_api_key, get_current_authuser
+from kallithea.lib.utils2 import check_password, generate_api_key, get_crypt_password, get_current_authuser
 from kallithea.model import db, forms, meta
 
 
@@ -72,7 +72,6 @@
         }
         # raises UserCreationError if it's not allowed
         check_allowed_create_user(user_data, cur_user)
-        from kallithea.lib.auth import get_crypt_password
 
         new_user = db.User()
         for k, v in form_data.items():
@@ -110,7 +109,6 @@
         if not cur_user:
             cur_user = getattr(get_current_authuser(), 'username', None)
 
-        from kallithea.lib.auth import check_password, get_crypt_password
         from kallithea.lib.hooks import check_allowed_create_user, log_create_user
         user_data = {
             'username': username, 'password': password,
@@ -194,7 +192,6 @@
                                    email_kwargs=email_kwargs)
 
     def update(self, user_id, form_data, skip_attrs=None):
-        from kallithea.lib.auth import get_crypt_password
         skip_attrs = skip_attrs or []
         user = self.get(user_id)
         if user.is_default_user:
@@ -215,8 +212,6 @@
                 setattr(user, k, v)
 
     def update_user(self, user, **kwargs):
-        from kallithea.lib.auth import get_crypt_password
-
         user = db.User.guess_instance(user)
         if user.is_default_user:
             raise DefaultUserException(
@@ -381,13 +376,12 @@
         return expected_token == token
 
     def reset_password(self, user_email, new_passwd):
-        from kallithea.lib import auth
         from kallithea.lib.celerylib import tasks
         user = db.User.get_by_email(user_email)
         if user is not None:
             if not self.can_change_password(user):
                 raise Exception('trying to change password for external user')
-            user.password = auth.get_crypt_password(new_passwd)
+            user.password = get_crypt_password(new_passwd)
             meta.Session().commit()
             log.info('change password for %s', user_email)
         if new_passwd is None:
--- a/kallithea/tests/functional/test_admin_users.py	Sun Nov 01 23:54:39 2020 +0100
+++ b/kallithea/tests/functional/test_admin_users.py	Sun Nov 01 23:50:29 2020 +0100
@@ -20,7 +20,7 @@
 import kallithea
 from kallithea.controllers.admin.users import UsersController
 from kallithea.lib import webutils
-from kallithea.lib.auth import check_password
+from kallithea.lib.utils2 import check_password
 from kallithea.model import db, meta, validators
 from kallithea.model.user import UserModel
 from kallithea.tests import base
--- a/kallithea/tests/functional/test_login.py	Sun Nov 01 23:54:39 2020 +0100
+++ b/kallithea/tests/functional/test_login.py	Sun Nov 01 23:50:29 2020 +0100
@@ -8,8 +8,7 @@
 
 import kallithea.lib.celerylib.tasks
 from kallithea.lib import webutils
-from kallithea.lib.auth import check_password
-from kallithea.lib.utils2 import generate_api_key
+from kallithea.lib.utils2 import check_password, generate_api_key
 from kallithea.model import db, meta, validators
 from kallithea.model.api_key import ApiKeyModel
 from kallithea.model.user import UserModel
--- a/kallithea/tests/scripts/manual_test_concurrency.py	Sun Nov 01 23:54:39 2020 +0100
+++ b/kallithea/tests/scripts/manual_test_concurrency.py	Sun Nov 01 23:50:29 2020 +0100
@@ -38,7 +38,7 @@
 from sqlalchemy import engine_from_config
 
 import kallithea.config.application
-from kallithea.lib.auth import get_crypt_password
+from kallithea.lib.utils2 import get_crypt_password
 from kallithea.model import db, meta
 from kallithea.model.base import init_model
 from kallithea.model.repo import RepoModel