--- a/bin/pvl.login-server Mon Jan 13 20:25:03 2014 +0200
+++ b/bin/pvl.login-server Mon Jan 13 20:25:36 2014 +0200
@@ -6,6 +6,8 @@
import pvl.args
+import pvl.ldap.args
+import pvl.login.auth
import pvl.login.server
import pvl.web.args
@@ -22,13 +24,18 @@
parser = optparse.OptionParser(main.__doc__)
parser.add_option_group(pvl.args.parser(parser))
parser.add_option_group(pvl.web.args.parser(parser))
+ parser.add_option_group(pvl.ldap.args.parser(parser))
options, args = parser.parse_args(argv[1:])
pvl.args.apply(options)
+ # ldap
+ ldap = pvl.ldap.args.apply(options)
+
# app
application = pvl.web.args.apply(options,
pvl.login.server.LoginApplication,
+ auth=pvl.login.auth.LDAPAuth(ldap),
)
# behind a reverse-proxy
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/pvl/login/auth.py Mon Jan 13 20:25:36 2014 +0200
@@ -0,0 +1,75 @@
+import ldap
+
+import pvl.ldap.domain
+
+import logging; log = logging.getLogger('pvl.login.auth')
+
+class AuthError (Exception) :
+ def __init__ (self, error) :
+ self.error = error
+
+ def __unicode__ (self) :
+ return u"Authenticating against the backend failed: {self.error}".format(self=self)
+
+class LDAPAuth (object) :
+ def __init__ (self, ldap) :
+ self.ldap = ldap
+
+ def auth (self, username, password) :
+ """
+ Attempt to bind against LDAP with given user object and password.
+
+ Returns None if the user does not seem to exist, False on invalid auth, True on valid auth.
+
+ Raises AuthError.
+ """
+
+ # search
+ try :
+ user = self.ldap.users.get(username)
+ except KeyError :
+ log.info("%s: not found", username)
+ return None
+ else :
+ log.info("%s: %s", username, user)
+
+ # bind
+ bind = self.bind(user, password)
+
+ if bind :
+ return True
+ else :
+ return False
+
+ def bind (self, user, password) :
+ """
+ Attempt to bind against LDAP with given user object and password.
+
+ Returns the bound connection, or
+ None - if the user does not seem toe xist
+ False - invalid auth
+
+ Raises AuthError.
+ """
+
+ conn = self.ldap.open()
+
+ try :
+ conn.bind(user.dn, password)
+
+ except ldap.INVALID_CREDENTIALS as ex :
+ log.info("%s: INVALID_CREDENTIALS", user)
+ return False
+
+ except ldap.NO_SUCH_OBJECT as ex :
+ log.info("%s: ldap.NO_SUCH_OBJECT", user)
+ return None
+
+ except ldap.LDAPError as ex :
+ log.exception("%s", user)
+ raise AuthError(ex)
+
+ else :
+ log.info("%s", user)
+ return conn
+
--- a/pvl/login/server.py Mon Jan 13 20:25:03 2014 +0200
+++ b/pvl/login/server.py Mon Jan 13 20:25:36 2014 +0200
@@ -5,6 +5,7 @@
import werkzeug
import werkzeug.urls
+import pvl.login.auth
import pvl.web
import pvl.web.response
@@ -185,6 +186,7 @@
lifetime = self.app.login_valid
valid = pubtkt.valid()
grace = pubtkt.grace()
+ grace_period = pubtkt.grace_period()
remaining = pubtkt.remaining()
if valid :
@@ -210,7 +212,7 @@
html.span(class_='glyphicon glyphicon-time'),
html.div(class_='progress pubtkt-progress',
data_start=valid.seconds,
- data_refresh=remaining.seconds if remaining else None,
+ data_refresh=grace_period.seconds if remaining else None,
data_end=lifetime.seconds,
)(
html.div(class_='progress-bar progress-bar-{status}'.format(status=status),
@@ -307,23 +309,34 @@
self.process_back()
except pubtkt.Error as ex :
self.alert('danger', ex)
+
+ if self.pubtkt :
+ self.username = self.pubtkt.uid
+ else :
+ self.username = None
# update cookie?
set_pubtkt = None
if self.request.method == 'POST' :
username = self.request.form.get('username')
- password = self.request.form.get('username')
+ password = self.request.form.get('password')
+
+ if username :
+ # preprocess
+ username = username.strip().lower()
if username and password :
- # preprocess
- username = username.strip().lower()
+ self.username = username
try :
set_pubtkt = self.app.auth(username, password)
- except pubtkt.Error as ex :
- self.auth_errors = ex
+ except pvl.login.auth.AuthError as ex :
+ self.alert('danger', "Internal authentication error, try again later?")
+
+ if not set_pubtkt :
+ self.alert('danger', "Invalid authentication credentials, try again.")
elif self.pubtkt and self.pubtkt.valid() :
# renew manually if valid
@@ -354,11 +367,6 @@
def render (self) :
- if self.pubtkt :
- username = self.pubtkt.uid
- else :
- username = None
-
domain = self.app.login_domain
if 'logout' in self.request.args :
@@ -390,12 +398,12 @@
html.div(class_='form-group')(
html.div(class_='input-group')(
html.label(for_='username', class_='sr-only')("Username"),
- html.input(name='username', type='text', class_='form-control', placeholder="username", required=True, autofocus=True, value=username),
+ html.input(name='username', type='text', class_='form-control', placeholder="username", required=True, autofocus=(not self.username), value=self.username),
html.span(class_='input-group-addon')("@{domain}".format(domain=domain)),
),
html.label(for_='password', class_='sr-only')("Password"),
- html.input(name='password', type='password', class_='form-control', placeholder="Password", required=(not renew)),
+ html.input(name='password', type='password', class_='form-control', placeholder="Password", required=(not renew), autofocus=bool(self.username)),
),
html.button(type='submit', class_='btn btn-primary')(
@@ -464,9 +472,10 @@
cookie_secure = True
cookie_httponly = True
- def __init__ (self, public_key=PUBLIC_KEY, private_key=PRIVATE_KEY, **opts) :
+ def __init__ (self, auth, public_key=PUBLIC_KEY, private_key=PRIVATE_KEY, **opts) :
super(LoginApplication, self).__init__(**opts)
+ self._auth = auth
self.server_keys = pubtkt.ServerKeys.config(
public_key = public_key,
private_key = private_key,
@@ -493,9 +502,16 @@
def auth (self, username, password) :
"""
- Perform authentication, returning a PubTkt, signed
+ Perform authentication, returning a PubTkt, signed, or None.
+
+ Raises auth.AuthError.
"""
+
+ auth = self._auth.auth(username, password)
+ if not auth :
+ return None
+
return pubtkt.PubTkt.new(username,
valid = self.login_valid,
grace = self.login_grace,
--- a/pvl/login/static/pubtkt-expire.js Mon Jan 13 20:25:03 2014 +0200
+++ b/pvl/login/static/pubtkt-expire.js Mon Jan 13 20:25:36 2014 +0200
@@ -33,14 +33,21 @@
var start_time = Date.now();
+ var reload = false;
+
function update_progress () {
var duration = (Date.now() - start_time) / 1000;
var progress = progress_start - duration;
+ if (reload) {
+ // delayed reload
+ window.location.reload(true);
+ }
+
if (progress <= 0 || (progress_refresh && progress < progress_refresh)) {
// done
- window.location.reload(true);
+ reload = true;
} else {
// update
progress_bar.css('width', (progress * 100 / progress_end) + '%');