pvl.login.server: separate redirect/refresh'd step for cert download to display html first; fix set-cookie quoting for werkzeug 0.9
--- a/pvl/login/server.py Tue Jan 14 23:14:53 2014 +0200
+++ b/pvl/login/server.py Tue Jan 14 23:15:36 2014 +0200
@@ -371,20 +371,21 @@
set_pubtkt = self.app.renew(self.pubtkt)
if set_pubtkt :
- # 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(set_pubtkt))
+ signed = self.app.sign(set_pubtkt)
self.pubtkt = set_pubtkt
+
+ # browsers and mod_pubtkt seem to be very particular about quoting ;'s in cookie values...
+ # this follows PHP's setcookie() encoding, without any quoting of the value..
+ cookie = '{cookie}={value}; Domain={domain}; Secure; HttpOnly'.format(
+ cookie = self.app.cookie_name,
+ value = werkzeug.urls.url_quote(signed),
+ domain = self.app.cookie_domain,
+ )
# redirect with cookie
response = pvl.web.response.redirect(self.back)
-
- response.set_cookie(self.app.cookie_name, cookie,
- domain = self.app.cookie_domain,
- secure = self.app.cookie_secure,
- httponly = self.app.cookie_httponly,
- )
+ response.headers.add('Set-Cookie', cookie)
return response
@@ -486,23 +487,72 @@
OUT = 'tmp/spkac'
+ def render_cert (self) :
+ return html.div(class_='container')(
+ self.render_alerts(),
+ html.div(class_='alert alert-success')(
+ "Your new SSL client cert has been signed, and should shortly be installed within your browser."
+ )
+ )
+
+ def respond_cert (self, cert) :
+ """
+ Generate a response for a signed cert, showing the user an informational page, and redirecting to the cert itself..
+ """
+
+ location = self.url(SSL, cert=cert)
+
+ return pvl.web.Response(
+ self.render_html(
+ body = self.render_cert(),
+ extrahead = html.meta(http_equiv='refresh', content='0;{location}'.format(location=location)),
+ ),
+ status = 200,
+ #headers = {
+ # 'Location': location
+ #},
+ mimetype = 'text/html',
+ )
+
def process_spkac (self, spkac) :
log.info("SPKAC: %s", spkac)
-
- cert = self.app.sign_ssl(self.pubtkt, spkac)
-
- file = self.response_file(open(cert))
+
+ try :
+ cert = self.app.ssl_sign(self.pubtkt, spkac)
+ except pvl.login.ssl.Error as ex :
+ self.alert('danger', ex)
+ return
+
+ log.info("Redirecting to client cert: %s", cert)
+ return self.respond_cert(cert)
- log.info("Returning client cert: %s", cert)
+ def process_cert (self, cert) :
+ """
+ Return user cert as download.
+
+ Uses the application/x-x509-user-cert mimetype per
+ https://developer.mozilla.org/en-US/docs/NSS_Certificate_Download_Specification
+ """
- return pvl.web.Response(file, mimetype='application/x-x509-user-cert')
+ try :
+ file = self.app.ssl_open(self.pubtkt, cert)
+ except pvl.login.ssl.Error as ex :
+ self.alert('danger', ex)
+ return
+
+ log.info("Returning client cert: %s", file)
- def process (self) :
+ return pvl.web.Response(self.response_file(file), mimetype='application/x-x509-user-cert')
+
+ def process (self, cert=None) :
if not self.process_cookie() :
return self.redirect(Login, back=self.url())
self.sslcert_dn = self.request.headers.get('X-Forwarded-SSL-DN')
+ if cert :
+ return self.process_cert(cert)
+
if self.request.method == 'POST' :
spkac = self.request.form.get('spkac')
@@ -536,6 +586,7 @@
# proto
urls.rule('/ssl', SSL),
+ urls.rule('/ssl/<cert>', SSL),
))
PUBLIC_KEY = 'etc/login/public.pem'
@@ -624,9 +675,13 @@
udata = self._auth.userdata(auth),
)
- def sign_ssl (self, pubtkt, spkac) :
+ def ssl_sign (self, pubtkt, spkac) :
"""
Generate a SSL client cert for the given user.
+
+ Returns the redirect token for downloading it.
+
+ Raises pvl.login.ssl.Error
"""
if not self._ssl :
@@ -635,3 +690,12 @@
return self._ssl.sign_user(pubtkt.uid, spkac,
userinfo = pubtkt.udata,
)
+
+ def ssl_open (self, pubtkt, cert) :
+ """
+ Open and return an SSL cert file.
+
+ Raises pvl.login.ssl.Error
+ """
+
+ return self._ssl.open_cert(pubtkt.uid, cert)
--- a/pvl/login/ssl.py Tue Jan 14 23:14:53 2014 +0200
+++ b/pvl/login/ssl.py Tue Jan 14 23:15:36 2014 +0200
@@ -93,7 +93,7 @@
"""
Sign given spkac string (base64-encoded) for given user.
- Returns path to the signed cert.
+ Returns a name for the signed cert.
"""
if not set(user).issubset(self.VALID_USER) :
@@ -119,7 +119,7 @@
# sign it
if os.path.exists(cert_file) :
log.warning("cert already exists: %s", cert_file)
- return cert_file
+ return name
if os.path.exists(tmp_file) :
log.warning("cleaning out previous tmp file: %s", tmp_file)
@@ -131,4 +131,19 @@
log.debug("%s: rename %s -> %s", user, tmp_file, cert_file)
os.rename(tmp_file, cert_file)
- return cert_file
+ return name
+
+ def open_cert (self, user, name) :
+ """
+ Return an opened cert file by username / cert name.
+ """
+
+ if not set(user).issubset(self.VALID_USER) :
+ raise Error("Invalid username: {user}".format(user=user))
+
+ path = os.path.join(self.users, user, name)
+
+ if not os.path.exists(path) :
+ raise Error("No cert found on server")
+
+ return open(path)