mirror of
https://github.com/xcat2/confluent.git
synced 2024-11-28 20:39:40 +00:00
Flesh out user password management
This commit is contained in:
parent
96ca623520
commit
b0855f7c88
@ -1,8 +1,39 @@
|
||||
# authentication and authorization routines for confluent
|
||||
# authentication scheme caches password values to help HTTP Basic auth
|
||||
# the PBKDF2 transform is skipped if a user has been idle for sufficient time
|
||||
|
||||
import confluent.config as config
|
||||
import eventlet
|
||||
import Crypto.Protocol.KDF as kdf
|
||||
import Crypto.Hash as hash
|
||||
|
||||
def authorize(name, element, tenant=None, access='rw'):
|
||||
_passcache = {}
|
||||
_passchecking = {}
|
||||
|
||||
def _get_usertenant(name, tenant=False):
|
||||
"""_get_usertenant
|
||||
|
||||
Convenience function to parse name into username and tenant.
|
||||
If tenant is explicitly passed in, then name must be the username
|
||||
tenant name with '/' is forbidden. If '/' is seen in name, tenant
|
||||
is assumed to preface the /.
|
||||
If the username is a tenant name, then it is to be the implied
|
||||
administrator account a tenant gets.
|
||||
Otherwise, just assume a user in the default tenant
|
||||
"""
|
||||
if isinstance(tenant,bool):
|
||||
user = name
|
||||
tenant = None
|
||||
elif '/' in name:
|
||||
tenant, user = name.split('/', 1)
|
||||
elif config.is_tenant(name):
|
||||
user = name
|
||||
tenant = name
|
||||
else:
|
||||
user = name
|
||||
tenant = None
|
||||
|
||||
def authorize(name, element, tenant=False, access='rw'):
|
||||
#TODO: actually use the element to ascertain if this user is good enough
|
||||
"""Determine whether the given authenticated name is authorized.
|
||||
|
||||
@ -16,20 +47,61 @@ def authorize(name, element, tenant=None, access='rw'):
|
||||
and the relevant ConfigManager object for the context of the
|
||||
request.
|
||||
"""
|
||||
if tenant is not None:
|
||||
user = name
|
||||
elif '/' in name:
|
||||
tenant, user = name.split('/', 1)
|
||||
elif config.is_tenant(name):
|
||||
user = name
|
||||
tenant = name
|
||||
else:
|
||||
user = name
|
||||
tenant = 0
|
||||
if not config.is_tenant(tenant):
|
||||
user, tenant = _get_usertenant(name, tenant)
|
||||
if tenant is not None and not config.is_tenant(tenant):
|
||||
return None
|
||||
configmanager = config.ConfigManager(tenant)
|
||||
userobj = configmanager.get_user(user)
|
||||
if userobj: #returning
|
||||
return (userobj, configmanager)
|
||||
return None
|
||||
|
||||
|
||||
def set_user_password(name, password, tenant=None):
|
||||
"""Set user password
|
||||
|
||||
:param name: The unique shortname of the user
|
||||
:param password: The password to set for given user
|
||||
:param tenant: The tenant to which the user belongs.
|
||||
"""
|
||||
# TODO(jbjohnso): WORKERPOOL
|
||||
# When worker pool implemented, hand off the
|
||||
# PBKDF2 to a worker instead of blocking
|
||||
user, tenant = _get_usertenant(name, tenant)
|
||||
_passcache[(user, tenant)] = password
|
||||
salt = os.urandom(8)
|
||||
crypted = kdf.PBKDF2(passphrase, salt, 32, 10000,
|
||||
lambda p, s: hash.HMAC.new(p, s, hash.SHA256).digest())
|
||||
cfm = config.ConfigManager(tenant)
|
||||
cfm.set_user(name, { 'cryptpass': (salt, crypted) })
|
||||
|
||||
|
||||
def check_user_passphrase(name, passphrase, tenant=None):
|
||||
user, tenant = _get_usertenant(name, tenant)
|
||||
if (user,tenant) in _passcache:
|
||||
if passphrase == passcache[(user,tenant)]:
|
||||
return True
|
||||
else:
|
||||
# In case of someone trying to guess,
|
||||
# while someone is legitimately logged in
|
||||
# invalidate cache and force the slower check
|
||||
del _passchache[(user, tenant)]
|
||||
return False
|
||||
eventlet.sleep(0.1) # limit throughput of remote guessing
|
||||
while (user,tenant) in _passchecking:
|
||||
# Want to serialize passphrase checking activity
|
||||
# by a user, which might be malicious
|
||||
# would normally make an event and wait
|
||||
# but here there's no need for that
|
||||
eventlet.sleep(0.5)
|
||||
cfm = config.ConfigManager(tenant)
|
||||
ucfg = cfm.get_user(user)
|
||||
if ucfg is None or 'cryptpass' not in ucfg:
|
||||
return False
|
||||
_passchecking[(user, tenant)] = True
|
||||
# TODO(jbjohnso): WORKERPOOL
|
||||
# PBKDF2 is, by design, cpu intensive
|
||||
# throw it at the worker pool when implemented
|
||||
salt, crypt = ucfg['cryptpass']
|
||||
crypted = kdf.PBKDF2(passphrase, salt, 32, 10000,
|
||||
lambda p, s: hash.HMAC.new(p, s, hash.SHA256).digest())
|
||||
|
@ -235,7 +235,19 @@ class ConfigManager(object):
|
||||
return None
|
||||
|
||||
|
||||
def create_user(self, name, role="Administrator", id=None, displayname=None):
|
||||
def set_user(self, name, attributemap):
|
||||
"""Set user attribute(s)
|
||||
|
||||
:param name: The login name of the user
|
||||
:param attributemap: A dict of key values to set
|
||||
"""
|
||||
user = self._cfgstore['users'][name]
|
||||
for attribute in attributemap:
|
||||
user[attribute] = attributemap[attribute]
|
||||
self._bg_sync_to_file()
|
||||
|
||||
def create_user(self, name,
|
||||
role="Administrator", id=None, displayname=None):
|
||||
"""Create a new user
|
||||
|
||||
:param name: The login name of the user
|
||||
|
Loading…
Reference in New Issue
Block a user