X-Git-Url: http://git.cascardo.info/?a=blobdiff_plain;f=ipsilon%2Fproviders%2Fsaml2idp.py;h=b0f430432b37c4f39a1a8badd8115f7c35acb274;hb=943158d19f879eb6ad515edeb59017671e4252c5;hp=a22a1f4139be464a2a909dd8e6dee12909bd76ce;hpb=953a4e418b1bdcbfddaf52d27a4cba9e9d8062e5;p=cascardo%2Fipsilon.git diff --git a/ipsilon/providers/saml2idp.py b/ipsilon/providers/saml2idp.py index a22a1f4..b0f4304 100755 --- a/ipsilon/providers/saml2idp.py +++ b/ipsilon/providers/saml2idp.py @@ -19,7 +19,14 @@ from ipsilon.providers.common import ProviderBase, ProviderPageBase from ipsilon.providers.saml2.auth import AuthenticateRequest +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 import lasso import os @@ -53,8 +60,8 @@ class Continue(AuthenticateRequest): session = UserSession() user = session.get_user() - session.nuke_data('login', 'Return') - self.stage = session.get_data('saml2', 'stage') + transdata = self.trans.retrieve() + self.stage = transdata['saml2_stage'] if user.is_anonymous: self._debug("User is marked anonymous?!") @@ -63,14 +70,14 @@ class Continue(AuthenticateRequest): self._debug('Continue auth for %s' % user.name) - dump = session.get_data('saml2', 'Request') - if not dump: + if 'saml2_request' not in transdata: self._debug("Couldn't find Request dump?!") # TODO: Return to SP with auth failed error raise cherrypy.HTTPError(400) + dump = transdata['saml2_request'] try: - login = lasso.Login.newFromDump(self.cfg.idp, dump) + login = self.cfg.idp.get_login_handler(dump) except Exception, e: # pylint: disable=broad-except self._debug('Failed to load status from dump: %r' % e) @@ -91,79 +98,78 @@ class SSO(ProviderPageBase): self.Continue = Continue(*args, **kwargs) +class Metadata(ProviderPageBase): + def GET(self, *args, **kwargs): + with open(self.cfg.idp_metadata_file) as m: + body = m.read() + cherrypy.response.headers["Content-Type"] = "text/xml" + cherrypy.response.headers["Content-Disposition"] = \ + 'attachment; filename="metadata.xml"' + return body + + class SAML2(ProviderPageBase): def __init__(self, *args, **kwargs): super(SAML2, self).__init__(*args, **kwargs) - - # Init IDP data - try: - self.cfg.idp = lasso.Server(self.cfg.idp_metadata_file, - self.cfg.idp_key_file, - None, - self.cfg.idp_certificate_file) - self.cfg.idp.role = lasso.PROVIDER_ROLE_IDP - except Exception, e: # pylint: disable=broad-except - self._debug('Failed to enable SAML2 provider: %r' % e) - return - - # Import all known applications - data = self.cfg.get_data() - for idval in data: - if 'type' not in data[idval] or data[idval]['type'] != 'SP': - continue - path = os.path.join(self.cfg.idp_storage_path, str(idval)) - sp = data[idval] - if 'name' in sp: - name = sp['name'] - else: - name = str(idval) - try: - meta = os.path.join(path, 'metadata.xml') - cert = os.path.join(path, 'certificate.pem') - self.cfg.idp.addProvider(lasso.PROVIDER_ROLE_SP, meta, cert) - self._debug('Added SP %s' % name) - except Exception, e: # pylint: disable=broad-except - self._debug('Failed to add SP %s: %r' % (name, e)) - + self.metadata = Metadata(*args, **kwargs) self.SSO = SSO(*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 - ] - } + 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'), + ) + if cherrypy.config.get('debug', False): + import logging + import sys + logger = logging.getLogger('lasso') + lh = logging.StreamHandler(sys.stderr) + logger.addHandler(lh) + logger.setLevel(logging.DEBUG) @property def allow_self_registration(self): @@ -188,6 +194,119 @@ Provides SAML 2.0 authentication infrastructure. """ return os.path.join(self.idp_storage_path, self.get_config_value('idp key file')) + @property + def default_allowed_nameids(self): + return self.get_config_value('default allowed nameids') + + @property + def default_nameid(self): + return self.get_config_value('default nameid') + + @property + def default_email_domain(self): + return self.get_config_value('default email domain') + def get_tree(self, site): + self.idp = self.init_idp() self.page = SAML2(site, self) + self.admin = Saml2AdminPage(site, self) return self.page + + def init_idp(self): + idp = None + # Init IDP data + try: + idp = IdentityProvider(self) + except Exception, e: # pylint: disable=broad-except + self._debug('Failed to init SAML2 provider: %r' % e) + return None + + # Import all known applications + data = self.get_data() + for idval in data: + sp = data[idval] + if 'type' not in sp or sp['type'] != 'SP': + continue + if 'name' not in sp or 'metadata' not in sp: + continue + try: + idp.add_provider(sp) + except Exception, e: # pylint: disable=broad-except + self._debug('Failed to add SP %s: %r' % (sp['name'], e)) + + return idp + + def on_enable(self): + super(IdpProvider, self).on_enable() + self.idp = self.init_idp() + if hasattr(self, 'admin'): + if self.admin: + self.admin.add_sps() + + +class Installer(object): + + 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', + help='Configure SAML2 Provider') + + def configure(self, opts): + if opts['saml2'] != 'yes': + return + + # Check storage path is present or create it + path = os.path.join(opts['data_dir'], 'saml2') + if not os.path.exists(path): + os.makedirs(path, 0700) + + # Use the same cert for signing and ecnryption for now + cert = Certificate(path) + cert.generate('idp', opts['hostname']) + + # Generate Idp Metadata + 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) + if 'krb' in opts and opts['krb'] == 'yes': + 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(*self.pargs) + po.name = 'saml2' + po.wipe_data() + po.wipe_config_values() + config = {'idp storage path': path, + 'idp metadata file': 'metadata.xml', + 'idp certificate file': cert.cert, + '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'])