mirror of
https://github.com/xcat2/confluent.git
synced 2025-01-15 12:17:47 +00:00
154 lines
6.0 KiB
Python
154 lines
6.0 KiB
Python
# authentication and authorization routines for confluent
|
|
# authentication scheme caches passphrase values to help HTTP Basic auth
|
|
# the PBKDF2 transform is skipped unless a user has been idle for sufficient
|
|
# time
|
|
|
|
import confluent.config.configmanager as configmanager
|
|
import eventlet
|
|
import Crypto.Protocol.KDF as kdf
|
|
import Crypto.Hash as hash
|
|
import os
|
|
import time
|
|
|
|
_passcache = {}
|
|
_passchecking = {}
|
|
|
|
|
|
def _prune_passcache():
|
|
# This function makes sure we don't remember a passphrase in memory more
|
|
# than 10 seconds
|
|
while (1):
|
|
curtime = time.time()
|
|
for passent in _passcache.iterkeys():
|
|
if passent[2] < curtime - 10:
|
|
del _passcache[passent]
|
|
eventlet.sleep(10)
|
|
|
|
|
|
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 not isinstance(tenant,bool):
|
|
# if not boolean, it must be explicit tenant
|
|
user = name
|
|
elif '/' in name: # tenant scoped name
|
|
tenant, user = name.split('/', 1)
|
|
elif configmanager.is_tenant(name):
|
|
# the account is the implicit tenant owner account
|
|
user = name
|
|
tenant = name
|
|
else: # assume it is a non-tenant user account
|
|
user = name
|
|
tenant = None
|
|
yield user
|
|
yield tenant
|
|
|
|
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.
|
|
|
|
:param name: The shortname authenticated by the authentication scheme
|
|
:param element: The path being examined.
|
|
:param tenant: The tenant under which the account exists (defaults to
|
|
detect from name)
|
|
:param access: Defaults to 'rw', can check 'ro' access
|
|
|
|
returns None if authorization fails or a tuple of the user object
|
|
and the relevant ConfigManager object for the context of the
|
|
request.
|
|
"""
|
|
user, tenant = _get_usertenant(name, tenant)
|
|
if tenant is not None and not configmanager.is_tenant(tenant):
|
|
return None
|
|
manager = configmanager.ConfigManager(tenant)
|
|
userobj = manager.get_user(user)
|
|
if userobj: #returning
|
|
return (userobj, manager)
|
|
return None
|
|
|
|
|
|
def set_user_passphrase(name, passphrase, tenant=None):
|
|
"""Set user passphrase
|
|
|
|
:param name: The unique shortname of the user
|
|
:param passphrase: The passphrase 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)] = passphrase
|
|
salt = os.urandom(8)
|
|
crypted = kdf.PBKDF2(passphrase, salt, 32, 10000,
|
|
lambda p, s: hash.HMAC.new(p, s, hash.SHA256).digest())
|
|
cfm = configmanager.ConfigManager(tenant)
|
|
cfm.set_user(name, { 'cryptpass': (salt, crypted) })
|
|
|
|
|
|
def check_user_passphrase(name, passphrase, element=None, tenant=False):
|
|
"""Check a a login name and passphrase for authenticity and authorization
|
|
|
|
The function combines authentication and authorization into one function.
|
|
It is highly recommended for a session layer to provide some secure means
|
|
of protecting a session once this function works once and calling
|
|
authorize() in order to provide best performance regardless of
|
|
circumstance. The function makes effort to provide good performance
|
|
in repeated invocation, but that facility will slow down to deter
|
|
detected passphrase guessing activity when such activity is detected.
|
|
|
|
:param name: The login name provided by client
|
|
:param passhprase: The passphrase provided by client
|
|
:param element: Optional specification of a particular destination
|
|
:param tenant: Optional explicit indication of tenant (defaults to
|
|
embedded in name)
|
|
"""
|
|
# If there is any sign of guessing on a user, all valid and
|
|
# invalid attempts are equally slowed to no more than 20 per second
|
|
# for that particular user.
|
|
# similarly, guessing usernames is throttled to 20/sec
|
|
user, tenant = _get_usertenant(name, tenant)
|
|
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)
|
|
if (user,tenant) in _passcache:
|
|
if passphrase == _passcache[(user,tenant)]:
|
|
return authorize(user, element, tenant)
|
|
else:
|
|
# In case of someone trying to guess,
|
|
# while someone is legitimately logged in
|
|
# invalidate cache and force the slower check
|
|
del _passcache[(user, tenant)]
|
|
return None
|
|
cfm = configmanager.ConfigManager(tenant)
|
|
ucfg = cfm.get_user(user)
|
|
if ucfg is None or 'cryptpass' not in ucfg:
|
|
eventlet.sleep(0.05) #stall even on test for existance of a username
|
|
return None
|
|
_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())
|
|
del _passchecking[(user, tenant)]
|
|
eventlet.sleep(0.05) #either way, we want to stall so that client can't
|
|
# determine failure because there is a delay, valid response will
|
|
# delay as well
|
|
if crypt == crypted:
|
|
_passcache[(user, tenant)] = passphrase
|
|
return authorize(user, element, tenant)
|
|
return None
|