pvl/login/server.py
changeset 348 089ec3eddc92
child 349 3c20473d0bdc
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pvl/login/server.py	Mon Jan 13 01:49:34 2014 +0200
@@ -0,0 +1,193 @@
+# encoding: utf-8
+
+import datetime
+import werkzeug
+import werkzeug.urls
+
+import pvl.web
+import pvl.web.response
+
+from pvl.login import pubtkt
+from pvl.web import urls, html
+
+import logging; log = logging.getLogger('pvl.login.server')
+
+class Handler (pvl.web.Handler) :
+    # Bootstrap
+    DOCTYPE = 'html'
+    HTML_XMLNS = None
+    HTML_LANG = 'en'
+    CSS = (
+            '//netdna.bootstrapcdn.com/bootstrap/3.0.3/css/bootstrap.min.css',
+    )
+    JS = (
+            '//code.jquery.com/jquery.js',
+            '//netdna.bootstrapcdn.com/bootstrap/3.0.3/js/bootstrap.min.js',
+    )
+
+    def process_cookie (self) :
+        """
+            Reverse the urlencoding used for the cookie...
+        """
+        
+        log.debug("cookies: %s", self.request.cookies)
+
+        cookie = self.request.cookies.get(self.app.cookie_name)
+        
+        log.debug("cookie %s=%s", self.app.cookie_name, cookie)
+
+        if cookie :
+            cookie = werkzeug.urls.url_unquote(cookie)
+        
+        log.debug("cookie decoded: %s", cookie)
+        
+        if cookie :
+            return self.app.load(cookie)
+
+class Index (Handler) :
+    TITLE = u"Päivölä Network Login"
+    
+    pubtkt = None
+    cookie_error = None
+
+    def process (self) :
+        try :
+            self.pubtkt = self.process_cookie()
+        except pubtkt.Error as ex :
+            self.cookie_error = ex
+
+    def render_info (self) :
+        if self.cookie_error :
+            return (
+                    html.h2("Invalid cookie"),
+                    html.p(self.cookie_error),
+            )
+        elif self.pubtkt :
+            return (
+                    html.h2("Login: {pubtkt.uid}".format(pubtkt=self.pubtkt)),
+            )
+        else :
+            return (
+                    html.a(href=self.url(Login), title="Login")(html.h2("No login")),
+            )
+   
+    def render (self) :
+
+        return html.div(class_='container')(
+                self.render_info(),
+        )
+
+class Login (Handler) :
+    TITLE = "Login"
+    
+    STYLE = """
+form#login {
+    max-width:  50%;
+    padding:    1em;
+    margin:     0 auto;
+}
+
+    """
+
+
+    auth_error = None
+
+    def process (self) :
+        if self.request.method == 'POST' :
+            back = self.app.login_server
+            username = self.request.form.get('username')
+            password = self.request.form.get('username')
+
+            if username and password :
+                # preprocess
+                username = username.strip().lower()
+                
+                try :
+                    pt = self.app.auth(username, password)
+
+                except pubtkt.Error as ex :
+                    self.auth_errors = ex
+
+                else :
+                    # browsers seem to be very particular about quoting ;'s in cookie values...
+                    # this follows PHP's setcookie() encoding...
+                    cookie = werkzeug.urls.url_quote(self.app.sign(pt))
+
+                    # redirect with cookie
+                    response = pvl.web.response.redirect(back)
+
+                    response.set_cookie(self.app.cookie_name, cookie,
+                        domain      = self.app.cookie_domain,
+                        secure      = self.app.cookie_secure,
+                        httponly    = self.app.cookie_httponly,
+                    )
+
+                    return response
+
+    def render (self) :
+        return html.div(class_='container')(
+            html.form(action=self.request.path, method='POST', id='login')(
+                html.fieldset(
+                    html.legend("Log in"),
+                
+                    html.div(class_='form-group')(
+                        html.label(for_='username', class_='sr-only')("Username"),
+                        html.input(name='username', type='text', class_='form-control', placeholder="username", required=True, autofocus=True),
+
+                        html.label(for_='password', class_='sr-only')("Password"),
+                        html.input(name='password', type='password', class_='form-control', placeholder="Password", required=True),
+                    ),
+
+                    html.button(type='submit', class_='btn btn-primary')("Login"),
+                )
+            )
+        )
+
+class LoginApplication (pvl.web.Application) :
+    URLS = urls.Map((
+        urls.rule('/',              Index),
+        urls.rule('/login',         Login),
+    ))
+
+    PUBLIC_KEY = 'etc/login/public.pem'
+    PRIVATE_KEY = 'etc/login/private.pem'
+
+    login_server = 'https://login.test.paivola.fi/'
+    login_expire = datetime.timedelta(hours=1)
+
+    cookie_name = 'auth_pubtkt'
+    cookie_domain = 'test.paivola.fi'
+    cookie_secure = True
+    cookie_httponly = True
+
+    def __init__ (self, public_key=PUBLIC_KEY, private_key=PRIVATE_KEY, **opts) :
+        super(LoginApplication, self).__init__(**opts)
+        
+        self.server_keys = pubtkt.ServerKeys.config(
+                public_key  = public_key,
+                private_key = private_key,
+        )
+
+    def load (self, cookie) :
+        """
+            Load a pubtkt from a cookie, and verify it.
+        """
+
+        return pubtkt.PubTkt.load(cookie, self.server_keys.public)
+
+    def auth (self, username, password) :
+        """
+            Perform authentication, returning a PubTkt, signed
+        """
+        
+        return pubtkt.PubTkt.new(username,
+                expiry  = self.login_expire,
+        )
+
+    def sign (self, pubtkt) :
+        """
+            Create a cookie by signing the given pubtkt.
+        """
+        
+        return pubtkt.sign(self.server_keys.private)
+