From 0d5c4ff5229880b6eba12a4e7e5bfbab2f415caa Mon Sep 17 00:00:00 2001 From: Simo Sorce Date: Mon, 19 Jan 2015 17:47:56 -0500 Subject: [PATCH] Add expiration to Idp metadata Also regenerate it frequently, so that any change in configuration can be automatically reflected in the metadata downloaded my clients over time. Signed-off-by: Simo Sorce Reviewed-by: Patrick Uiterwijk --- ipsilon/providers/saml2idp.py | 37 ++++++++++++++++++++++++++++++----- ipsilon/util/page.py | 10 +++++++++- 2 files changed, 41 insertions(+), 6 deletions(-) diff --git a/ipsilon/providers/saml2idp.py b/ipsilon/providers/saml2idp.py index 298a205..1778307 100644 --- a/ipsilon/providers/saml2idp.py +++ b/ipsilon/providers/saml2idp.py @@ -26,8 +26,10 @@ from ipsilon.util.user import UserSession from ipsilon.util.plugin import PluginObject from ipsilon.util import config as pconfig import cherrypy +from datetime import timedelta import lasso import os +import time class Redirect(AuthenticateRequest): @@ -96,15 +98,39 @@ class SSO(ProviderPageBase): self.Continue = Continue(*args, **kwargs) +# one week +METADATA_RENEW_INTERVAL = 60 * 60 * 24 * 7 +# 30 days +METADATA_VALIDITY_PERIOD = 30 + + class Metadata(ProviderPageBase): def GET(self, *args, **kwargs): - with open(self.cfg.idp_metadata_file) as m: - body = m.read() + + body = self._get_metadata() cherrypy.response.headers["Content-Type"] = "text/xml" cherrypy.response.headers["Content-Disposition"] = \ 'attachment; filename="metadata.xml"' return body + def _get_metadata(self): + if os.path.isfile(self.cfg.idp_metadata_file): + s = os.stat(self.cfg.idp_metadata_file) + if s.st_mtime > time.time() - METADATA_RENEW_INTERVAL: + with open(self.cfg.idp_metadata_file) as m: + return m.read() + + # Otherwise generate and save + idp_cert = Certificate() + idp_cert.import_cert(self.cfg.idp_certificate_file, + self.cfg.idp_key_file) + meta = IdpMetadataGenerator(self.instance_base_url(), idp_cert, + timedelta(METADATA_VALIDITY_PERIOD)) + body = meta.output() + with open(self.cfg.idp_metadata_file, 'w+') as m: + m.write(body) + return body + class SAML2(ProviderPageBase): @@ -244,8 +270,8 @@ Provides SAML 2.0 authentication infrastructure. """ class IdpMetadataGenerator(object): - def __init__(self, url, idp_cert): - self.meta = metadata.Metadata(metadata.IDP_ROLE) + def __init__(self, url, idp_cert, expiration=None): + self.meta = metadata.Metadata(metadata.IDP_ROLE, expiration) self.meta.set_entity_id('%s/saml2/metadata' % url) self.meta.add_certs(idp_cert, idp_cert) self.meta.add_service(metadata.SAML2_SERVICE_MAP['sso-post'], @@ -292,7 +318,8 @@ class Installer(object): if opts['secure'].lower() == 'no': proto = 'http' url = '%s://%s/%s' % (proto, opts['hostname'], opts['instance']) - meta = IdpMetadataGenerator(url, cert) + meta = IdpMetadataGenerator(url, cert, + timedelta(METADATA_VALIDITY_PERIOD)) if 'krb' in opts and opts['krb'] == 'yes': meta.meta.add_allowed_name_format( lasso.SAML2_NAME_IDENTIFIER_FORMAT_KERBEROS) diff --git a/ipsilon/util/page.py b/ipsilon/util/page.py index 0929961..d2ccb51 100644 --- a/ipsilon/util/page.py +++ b/ipsilon/util/page.py @@ -51,6 +51,14 @@ class Page(Log): self.default_headers = dict() self.auth_protect = False + def get_url(self): + return cherrypy.url(relative=False) + + def instance_base_url(self): + url = self.get_url() + s = urlparse(unquote(url)) + return '%s://%s%s' % (s.scheme, s.netloc, self.basepath) + def _check_referer(self, referer, url): r = urlparse(unquote(referer)) u = urlparse(unquote(url)) @@ -82,7 +90,7 @@ class Page(Log): if callable(op): # Basic CSRF protection if cherrypy.request.method != 'GET': - url = cherrypy.url(relative=False) + url = self.get_url() if 'referer' not in cherrypy.request.headers: self._debug("Missing referer in %s request to %s" % (cherrypy.request.method, url)) -- 2.20.1