--- a/pvl/login/server.py Mon Jan 13 03:23:33 2014 +0200
+++ b/pvl/login/server.py Mon Jan 13 17:11:09 2014 +0200
@@ -1,6 +1,7 @@
# encoding: utf-8
import datetime
+import urlparse
import werkzeug
import werkzeug.urls
@@ -25,12 +26,30 @@
'//netdna.bootstrapcdn.com/bootstrap/3.0.3/js/bootstrap.min.js',
)
+ STYLE = """
+body {
+ padding-top: 2em;
+ text-align: center;
+}
+
+.container {
+ padding: 2em 1em;
+ text-align: left;
+}
+ """
+
def redirect (self, *url, **params) :
return pvl.web.response.redirect(self.url(*url, **params))
-
+
pubtkt = None
- cookie_error = None
- verify_error = None
+
+ def init (self) :
+ self.alerts = []
+
+ def alert (self, type, alert) :
+ log.info(u"%s: %s", type, alert)
+
+ self.alerts.append((type, unicode(alert)))
def process_cookie (self) :
"""
@@ -57,15 +76,51 @@
self.pubtkt = self.app.load(cookie)
except pubtkt.ParseError as ex :
- self.cookie_error = ex
+ self.alert('danger', ex)
+
+ except pubtkt.ExpiredError as ex :
+ self.pubtkt = ex.pubtkt
+ self.alert('warning', ex)
except pubtkt.VerifyError as ex :
self.pubtkt = ex.pubtkt
- self.verify_error = ex
-
+ self.alert('danger', ex)
+
+ def process_back (self) :
+ self.server = None
+ self.back = urlparse.urlunparse((self.app.login_scheme, self.app.login_server, '/', '', '', ''))
+
+ back = self.request.args.get('back')
+
+ if back :
+ url = urlparse.urlparse(back, self.app.login_scheme)
+
+ if not self.app.login_scheme :
+ scheme = url.scheme
+
+ elif url.scheme == self.app.login_scheme :
+ scheme = url.scheme
+
+ else :
+ self.alert('info', "Using SSL for application URL")
+ scheme = self.app.login_scheme
+
+ self.server = self.app.check_server(url.hostname)
+ self.back = urlparse.urlunparse((scheme, self.server, url.path, url.params, url.query, url.fragment))
class Index (Handler) :
TITLE = u"Päivölä Network Login"
+
+ STYLE = Handler.STYLE + """
+.pubtkt {
+ width: 30em;
+ margin: 1em auto;
+}
+
+.pubtkt form {
+ display: inline;
+}
+ """
def process (self) :
self.process_cookie()
@@ -105,10 +160,10 @@
grace = pubtkt.grace()
if valid and grace :
- valid = "{valid} ({grace})".format(valid=self.render_valid(valid), grace=self.render_valid(grace))
+ valid = "{grace} ({valid})".format(valid=self.render_valid(valid), grace=self.render_valid(grace))
valid_status = 'success'
elif valid and grace is False :
- valid = "Renewable ({grace})".format(valid=self.render_valid(valid), grace=self.render_valid(grace))
+ valid = "Renewable ({valid})".format(valid=self.render_valid(valid))
valid_status = 'warning'
elif valid :
valid = "{valid}".format(valid=self.render_valid(valid))
@@ -134,7 +189,7 @@
def render_pubtkt (self, pubtkt) :
status = self.render_status(pubtkt)
- return html.div(class_='panel panel-{status}'.format(status=status))(
+ return html.div(class_='pubtkt panel panel-{status}'.format(status=status))(
html.div(class_='panel-heading')("Login: {pubtkt.uid}".format(pubtkt=self.pubtkt)),
html.ul(class_='list-group')(
html.li(class_='list-group-item {status}'.format(status=('alert-'+status if status else '')), title=title)(
@@ -143,30 +198,30 @@
) for icon, status, title, info in self.render_pubtkt_fields(pubtkt)
),
html.div(class_='panel-footer')(
- (
- html.form(action=self.url(Login), method='post')(
- html.button(type='submit', class_='btn btn-success')("Renew"),
- )
- ) if pubtkt.valid() else (
- html.form(action=self.url(Login), method='get')(
- html.button(type='submit', class_='btn btn-info')("Login"),
+ #html.div(class_='btn-toolbar', role='toolbar')(
+ (
+ html.form(action=self.url(Login), method='post', class_='form-inline')(
+ html.button(type='submit', class_='btn btn-success')("Renew"),
+ )
+ ) if pubtkt.valid() else (
+ html.form(action=self.url(Login), method='get', class_='form-inline')(
+ html.button(type='submit', class_='btn btn-info')("Login"),
+ ),
),
- ),
- html.form(action=self.url(Logout), method='post')(
- html.button(type='submit', class_='btn btn-warning')("Logout"),
- ),
+ html.form(action=self.url(Logout), method='post', class_='form-inline pull-right')(
+ html.button(type='submit', class_='btn btn-warning')("Logout"),
+ ),
+ #),
),
)
def render_info (self) :
- if self.cookie_error :
- return (
- html.h2("Invalid cookie"),
- html.p(self.cookie_error),
- )
+ for type, alert in self.alerts :
+ yield html.div(class_='alert alert-{type}'.format(type=type))(alert)
- return self.render_pubtkt(self.pubtkt)
+ if self.pubtkt :
+ yield self.render_pubtkt(self.pubtkt)
def render (self) :
@@ -177,7 +232,7 @@
class Login (Handler) :
TITLE = "Login"
- STYLE = """
+ STYLE = Handler.STYLE + """
form#login {
max-width: 50%;
padding: 1em;
@@ -185,12 +240,15 @@
}
"""
-
def process (self) :
self.process_cookie()
+
+ try :
+ self.process_back()
+ except pubtkt.Error as ex :
+ self.alert('danger', ex)
if self.request.method == 'POST' :
- back = self.app.login_server
username = self.request.form.get('username')
password = self.request.form.get('username')
@@ -216,7 +274,7 @@
cookie = werkzeug.urls.url_quote(self.app.sign(self.pubtkt))
# redirect with cookie
- response = pvl.web.response.redirect(back)
+ response = pvl.web.response.redirect(self.back)
response.set_cookie(self.app.cookie_name, cookie,
domain = self.app.cookie_domain,
@@ -235,9 +293,21 @@
domain = self.app.login_domain
return html.div(class_='container')(
- html.form(action=self.url(), method='POST', id='login')(
+ html.form(action=self.url(back=self.back), method='POST', id='login')(
+ (
+ html.div(class_='alert alert-{alert}'.format(alert=type))(alert)
+ for type, alert in self.alerts
+ ),
+
html.fieldset(
- html.legend("Log in"),
+ html.legend(
+ (
+ "Login @ ",
+ html.a(href=self.back)(self.server),
+ ) if self.server else (
+ "Login"
+ )
+ ),
html.div(class_='form-group')(
html.div(class_='input-group')(
@@ -260,14 +330,19 @@
def process (self) :
self.process_cookie()
+
+ try :
+ self.process_back()
+ except pubtkt.Error as ex :
+ self.alert('danger', ex)
if not self.pubtkt :
- return self.redirect(Index)
+ return self.redirect(self.back)
if self.request.method == 'POST' :
back = self.app.login_server
- response = pvl.web.response.redirect(back)
+ response = pvl.web.response.redirect(self.back)
response.set_cookie(self.app.cookie_name, '',
expires = 0,
@@ -300,8 +375,10 @@
PRIVATE_KEY = 'etc/login/private.pem'
login_domain = 'test.paivola.fi'
- login_server = 'https://login.test.paivola.fi/'
- login_expire = datetime.timedelta(seconds=60)
+ login_server = 'login.test.paivola.fi'
+ login_valid = datetime.timedelta(seconds=60)
+ login_grace = datetime.timedelta(seconds=30)
+ login_scheme = 'https'
cookie_name = 'auth_pubtkt'
cookie_domain = 'test.paivola.fi'
@@ -316,6 +393,18 @@
private_key = private_key,
)
+ def check_server (self, server) :
+ """
+ Check that the given target server is valid.
+ """
+
+ server = server.lower()
+
+ if server == self.login_domain or server.endswith('.' + self.login_domain) :
+ return server
+ else :
+ raise pubtkt.ServerError("Target server is not covered by our authentication domain: {domain}".format(domain=self.login_domain))
+
def load (self, cookie) :
"""
Load a pubtkt from a cookie, and verify it.
@@ -329,7 +418,8 @@
"""
return pubtkt.PubTkt.new(username,
- expiry = self.login_expire,
+ valid = self.login_valid,
+ grace = self.login_grace,
)
def sign (self, pubtkt) :
@@ -344,4 +434,4 @@
Renew and re-sign the given pubtkt.
"""
- pubtkt.renew(self.login_expire)
+ pubtkt.renew(self.login_valid, self.login_grace)