Commit 26f169cd authored by ale's avatar ale

optional TOTP support (for now only implemented in the machdb backend)

parent c9f96da1
......@@ -28,7 +28,7 @@ class AuthBase(object):
def __init__(self, config):
self.config = config
def authenticate(self, username, password):
def authenticate(self, username, password, otp=None):
return False
def match_groups(self, username, groups):
......
......@@ -58,7 +58,7 @@ class Auth(AuthBase):
except AttributeError:
self.rdn_fmt = 'uid=%s'
def authenticate(self, username, password):
def authenticate(self, username, password, otp=None):
if not re.search(r'^[-a-zA-Z0-9.]+$', username):
return False
if not password:
......
......@@ -28,13 +28,16 @@ class Updater(threading.Thread):
time.sleep(600)
def update_auth_cache(self):
pwcache = {}
grpcache = {}
pwcache, otpcache, grpcache = {}, {}, {}
for user in mdb.User.find():
if user.enabled:
pwcache[user.name] = user.password
grpcache[user.name] = set(x.name for x in user.groups)
if not user.enabled:
continue
pwcache[user.name] = user.password
if user.totp_key:
otpcache[user.name] = user.totp_key
grpcache[user.name] = set(x.name for x in user.groups)
self.auth_cache['pwcache'] = pwcache
self.auth_cache['otpcache'] = otpcache
self.auth_cache['grpcache'] = grpcache
......@@ -47,10 +50,16 @@ class Auth(AuthBase):
updater.setDaemon(True)
updater.start()
def authenticate(self, username, password):
def authenticate(self, username, password, otp=None):
pwcache = self.auth_cache['pwcache']
totp_key = self.auth_cache['otpcache'].get(username)
if (username in pwcache and
crypt.crypt(password, pwcache[username]) == pwcache[username]):
if totp_key:
ok, drift = accept_totp(totp_key, otp or '', format='dec6',
period=30, forward_drift=1,
backward_drift=1)
return ok
return True
return False
......
......@@ -53,7 +53,7 @@ class Auth(AuthBase):
except AttributeError:
self.service = "sso"
def authenticate(self, username, password):
def authenticate(self, username, password, otp=None):
pam = PAM.pam()
pam.start(self.service)
pam.set_item(PAM.PAM_USER, username)
......
......@@ -34,7 +34,7 @@ class Auth(AuthBase):
def __init__(self, config):
self.config = config
def authenticate(self, u, p):
def authenticate(self, u, p, otp=None):
if u == 'error':
raise KeyError('blah!')
return (u and u == p)
......
......@@ -34,6 +34,9 @@ class LoginService(object):
SSO_DOMAIN
SSO domain (this currently acts as a shared secret).
SSO_ENABLE_OTP
Enable (T)OTP for non-SSO authentication requests.
SSO_AUTH_MODULE
Authentication plugin. This should be the Python module path to
something which implements the so_server.auth.AuthBase interface.
......@@ -83,6 +86,7 @@ class LoginService(object):
__import__(config['SSO_AUTH_MODULE'])
self.auth = sys.modules[config['SSO_AUTH_MODULE']].Auth(config)
self.enable_otp = config.get('SSO_ENABLE_OTP', False)
# Run a sanity check before returning.
self._sanity_check()
......@@ -163,9 +167,12 @@ class LoginService(object):
else:
return set()
def authenticate(self, username, password):
def authenticate(self, username, password, otp=None):
"""Authenticate a user."""
try:
return self.auth.authenticate(username, password)
if self.enable_otp:
return self.auth.authenticate(username, password, otp)
else:
return self.auth.authenticate(username, password)
except:
log.exception('Unhandled exception in authenticate()')
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment