X-Git-Url: http://git.cascardo.info/?a=blobdiff_plain;f=ipsilon%2Fproviders%2Fsaml2idp.py;h=9fa2fd6d7523b402e829bfb783afef69029162ea;hb=db88788fe906f315733b6ae67929f62cfc307d24;hp=e30e4a1cac41da88e579274242d2a5d9a6ca55c7;hpb=b4bcb99e3217e658c1277cd5d484fa0c62c7aa0c;p=cascardo%2Fipsilon.git diff --git a/ipsilon/providers/saml2idp.py b/ipsilon/providers/saml2idp.py old mode 100755 new mode 100644 index e30e4a1..9fa2fd6 --- a/ipsilon/providers/saml2idp.py +++ b/ipsilon/providers/saml2idp.py @@ -1,5 +1,3 @@ -#!/usr/bin/python -# # Copyright (C) 2014 Simo Sorce # # see file 'COPYING' for use and warranty information @@ -18,18 +16,21 @@ # along with this program. If not, see . from ipsilon.providers.common import ProviderBase, ProviderPageBase -from ipsilon.providers.common import FACILITY from ipsilon.providers.saml2.auth import AuthenticateRequest -from ipsilon.providers.saml2.admin import AdminPage +from ipsilon.providers.saml2.logout import LogoutRequest +from ipsilon.providers.saml2.admin import Saml2AdminPage from ipsilon.providers.saml2.provider import IdentityProvider from ipsilon.tools.certs import Certificate from ipsilon.tools import saml2metadata as metadata from ipsilon.tools import files 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): @@ -89,6 +90,19 @@ class Continue(AuthenticateRequest): return self.auth(login) +class RedirectLogout(LogoutRequest): + + def GET(self, *args, **kwargs): + query = cherrypy.request.query_string + + relaystate = kwargs.get(lasso.SAML2_FIELD_RELAYSTATE) + response = kwargs.get(lasso.SAML2_FIELD_RESPONSE) + + return self.logout(query, + relaystate=relaystate, + samlresponse=response) + + class SSO(ProviderPageBase): def __init__(self, *args, **kwargs): @@ -98,15 +112,47 @@ class SSO(ProviderPageBase): self.Continue = Continue(*args, **kwargs) +class SLO(ProviderPageBase): + + def __init__(self, *args, **kwargs): + super(SLO, self).__init__(*args, **kwargs) + self._debug('SLO init') + self.Redirect = RedirectLogout(*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): @@ -114,60 +160,64 @@ class SAML2(ProviderPageBase): super(SAML2, self).__init__(*args, **kwargs) self.metadata = Metadata(*args, **kwargs) self.SSO = SSO(*args, **kwargs) + self.SLO = SLO(*args, **kwargs) class IdpProvider(ProviderBase): - def __init__(self): - super(IdpProvider, self).__init__('saml2', 'saml2') + def __init__(self, *pargs): + super(IdpProvider, self).__init__('saml2', 'saml2', *pargs) self.admin = None self.page = None self.idp = None self.description = """ Provides SAML 2.0 authentication infrastructure. """ - self._options = { - 'idp storage path': [ - """ Path to data storage accessible by the IdP """, - 'string', - '/var/lib/ipsilon/saml2' - ], - 'idp metadata file': [ - """ The IdP Metadata file genearated at install time. """, - 'string', - 'metadata.xml' - ], - 'idp certificate file': [ - """ The IdP PEM Certificate genearated at install time. """, - 'string', - 'certificate.pem' - ], - 'idp key file': [ - """ The IdP Certificate Key genearated at install time. """, - 'string', - 'certificate.key' - ], - 'allow self registration': [ - """ Allow authenticated users to register applications. """, - 'boolean', - True - ], - 'default allowed nameids': [ - """Default Allowed NameIDs for Service Providers. """, - 'list', - ['persistent', 'transient', 'email', 'kerberos', 'x509'] - ], - 'default nameid': [ - """Default NameID used by Service Providers. """, - 'string', - 'persistent' - ], - 'default email domain': [ - """Default email domain, for users missing email property.""", - 'string', - 'example.com' - ] - } + self.new_config( + self.name, + pconfig.String( + 'idp storage path', + 'Path to data storage accessible by the IdP.', + '/var/lib/ipsilon/saml2'), + pconfig.String( + 'idp metadata file', + 'The IdP Metadata file genearated at install time.', + 'metadata.xml'), + pconfig.String( + 'idp certificate file', + 'The IdP PEM Certificate genearated at install time.', + 'certificate.pem'), + pconfig.String( + 'idp key file', + 'The IdP Certificate Key genearated at install time.', + 'certificate.key'), + pconfig.Condition( + 'allow self registration', + 'Allow authenticated users to register applications.', + True), + pconfig.Choice( + 'default allowed nameids', + 'Default Allowed NameIDs for Service Providers.', + metadata.SAML2_NAMEID_MAP.keys(), + ['persistent', 'transient', 'email', 'kerberos', 'x509']), + pconfig.Pick( + 'default nameid', + 'Default NameID used by Service Providers.', + metadata.SAML2_NAMEID_MAP.keys(), + 'persistent'), + pconfig.String( + 'default email domain', + 'Used for users missing the email property.', + 'example.com'), + pconfig.MappingList( + 'default attribute mapping', + 'Defines how to map attributes before returning them to SPs', + [['*', '*']]), + pconfig.ComplexList( + 'default allowed attributes', + 'Defines a list of allowed attributes, applied after mapping', + ['*']), + ) if cherrypy.config.get('debug', False): import logging import sys @@ -211,10 +261,18 @@ Provides SAML 2.0 authentication infrastructure. """ def default_email_domain(self): return self.get_config_value('default email domain') + @property + def default_attribute_mapping(self): + return self.get_config_value('default attribute mapping') + + @property + def default_allowed_attributes(self): + return self.get_config_value('default allowed attributes') + def get_tree(self, site): self.idp = self.init_idp() self.page = SAML2(site, self) - self.admin = AdminPage(site, self) + self.admin = Saml2AdminPage(site, self) return self.page def init_idp(self): @@ -242,17 +300,42 @@ Provides SAML 2.0 authentication infrastructure. """ return idp def on_enable(self): - self.init_idp() + super(IdpProvider, self).on_enable() + self.idp = self.init_idp() if hasattr(self, 'admin'): if self.admin: self.admin.add_sps() +class IdpMetadataGenerator(object): + + 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'], + '%s/saml2/SSO/POST' % url) + self.meta.add_service(metadata.SAML2_SERVICE_MAP['sso-redirect'], + '%s/saml2/SSO/Redirect' % url) + self.meta.add_service(metadata.SAML2_SERVICE_MAP['logout-redirect'], + '%s/saml2/SLO/Redirect' % url) + self.meta.add_allowed_name_format( + lasso.SAML2_NAME_IDENTIFIER_FORMAT_TRANSIENT) + self.meta.add_allowed_name_format( + lasso.SAML2_NAME_IDENTIFIER_FORMAT_PERSISTENT) + self.meta.add_allowed_name_format( + lasso.SAML2_NAME_IDENTIFIER_FORMAT_EMAIL) + + def output(self, path=None): + return self.meta.output(path) + + class Installer(object): - def __init__(self): + def __init__(self, *pargs): self.name = 'saml2' self.ptype = 'provider' + self.pargs = pargs def install_args(self, group): group.add_argument('--saml2', choices=['yes', 'no'], default='yes', @@ -275,40 +358,29 @@ class Installer(object): proto = 'https' if opts['secure'].lower() == 'no': proto = 'http' - url = '%s://%s/%s/saml2' % (proto, opts['hostname'], opts['instance']) - meta = metadata.Metadata(metadata.IDP_ROLE) - meta.set_entity_id(url + '/metadata') - meta.add_certs(cert, cert) - meta.add_service(metadata.SAML2_SERVICE_MAP['sso-post'], - url + '/SSO/POST') - meta.add_service(metadata.SAML2_SERVICE_MAP['sso-redirect'], - url + '/SSO/Redirect') - - meta.add_allowed_name_format( - lasso.SAML2_NAME_IDENTIFIER_FORMAT_TRANSIENT) - meta.add_allowed_name_format( - lasso.SAML2_NAME_IDENTIFIER_FORMAT_PERSISTENT) - meta.add_allowed_name_format( - lasso.SAML2_NAME_IDENTIFIER_FORMAT_EMAIL) + url = '%s://%s/%s' % (proto, opts['hostname'], opts['instance']) + meta = IdpMetadataGenerator(url, cert, + timedelta(METADATA_VALIDITY_PERIOD)) if 'krb' in opts and opts['krb'] == 'yes': - meta.add_allowed_name_format( + meta.meta.add_allowed_name_format( lasso.SAML2_NAME_IDENTIFIER_FORMAT_KERBEROS) meta.output(os.path.join(path, 'metadata.xml')) # Add configuration data to database - po = PluginObject() + po = PluginObject(*self.pargs) po.name = 'saml2' po.wipe_data() - - po.wipe_config_values(FACILITY) + po.wipe_config_values() config = {'idp storage path': path, 'idp metadata file': 'metadata.xml', 'idp certificate file': cert.cert, - 'idp key file': cert.key, - 'enabled': '1'} - po.set_config(config) - po.save_plugin_config(FACILITY) + 'idp key file': cert.key} + po.save_plugin_config(config) + + # Update global config to add login plugin + po.is_enabled = True + po.save_enabled_state() # Fixup permissions so only the ipsilon user can read these files files.fix_user_dirs(path, opts['system_user'])