pvl/login/server.py
changeset 354 d46c8d3e3140
parent 351 147f5e86b139
child 355 2daf32a118ff
--- 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)