From f9269d30b199ee77ec4dc1e59f3006481b46803a Mon Sep 17 00:00:00 2001 From: John Dennis Date: Nov 25 2015 03:19:49 +0000 Subject: Allow SAML SP metadata to be viewed and edited After an SP is created it is impossible to view the metadata associated with the SP. Being able to view the metadata is essential for diagnosing problems because the SP metadata is the configuration for the SP. It is also useful to be able to update the SP's metadata. Add Metadata as a config item, for some reason it never existed. The Metadata config item must be present for the options_config.html template can access it. Assure only the admin can update the metadata. Add a new utility function validate_sp_metadata() that attempts to assure the metadata is valid, call this from all the locations where metadata is loaded. Add the 'multiline' property to the String config class, multiline directs options_config.html to generate a textarea widget instead of single line text input. Ticket: 130 Signed-off-by: John Dennis --- diff --git a/ipsilon/providers/saml2/admin.py b/ipsilon/providers/saml2/admin.py index c7a0289..133f647 100644 --- a/ipsilon/providers/saml2/admin.py +++ b/ipsilon/providers/saml2/admin.py @@ -11,6 +11,8 @@ from ipsilon.admin.common import get_complex_list_value from ipsilon.providers.saml2.provider import ServiceProvider from ipsilon.providers.saml2.provider import ServiceProviderCreator from ipsilon.providers.saml2.provider import InvalidProviderId +from ipsilon.providers.saml2.provider import validate_sp_metadata +from ipsilon.providers.saml2.provider import InvalidProviderMetadata from copy import deepcopy import requests import logging @@ -227,7 +229,8 @@ class SPAdminPage(AdminPage): 'Allowed NameIDs', 'Attribute Mapping', 'Allowed Attributes', 'Description', 'Service Provider link', - 'Visible in Portal', 'Image File']: + 'Visible in Portal', 'Image File', + 'Metadata']: if not self.user.is_admin: raise UnauthorizedUser( "Unauthorized to set %s" % key @@ -277,7 +280,14 @@ class SPAdminPage(AdminPage): raise InvalidValueFormat( 'Invalid Image file format' ) + elif name == 'Metadata': + validate_sp_metadata(value) + self.sp.metadata = value + except InvalidProviderMetadata, e: + message = str(e) + message_type = ADMIN_STATUS_WARN + return self.root_with_msg(message, message_type) except InvalidValueFormat, e: message = str(e) message_type = ADMIN_STATUS_WARN diff --git a/ipsilon/providers/saml2/provider.py b/ipsilon/providers/saml2/provider.py index 6d46ad2..7a13da1 100644 --- a/ipsilon/providers/saml2/provider.py +++ b/ipsilon/providers/saml2/provider.py @@ -15,12 +15,20 @@ VALID_IN_NAME = r'[^\ a-zA-Z0-9]' class InvalidProviderId(ProviderException): - def __init__(self, code): - message = 'Invalid Provider ID: %s' % code + def __init__(self, msg): + message = 'Invalid Provider ID: %s' % msg super(InvalidProviderId, self).__init__(message) self.debug(message) +class InvalidProviderMetadata(ProviderException): + + def __init__(self, msg): + message = 'Invalid Provider Metadata: %s' % msg + super(InvalidProviderMetadata, self).__init__(message) + self.debug(message) + + class NameIdNotAllowed(Exception): def __init__(self, nid): @@ -32,6 +40,30 @@ class NameIdNotAllowed(Exception): return repr(self.message) +def validate_sp_metadata(metadata): + '''Validate SP metadata + + Attempt to load the metadata into Lasso and verify it loads. + Assure only 1 provider is included in the metadata and return + it's id. If not valid raise an exception. + + Note, loading the metadata into Lasso is a weak check, basically + all that Lasso does is to parse the XML, it doesn't verify the + contents until first use. + ''' + test = lasso.Server() + try: + test.addProviderFromBuffer(lasso.PROVIDER_ROLE_SP, metadata) + except Exception as e: # pylint: disable=broad-except + raise InvalidProviderMetadata(str(e)) + newsps = test.get_providers() + if len(newsps) != 1: + raise InvalidProviderMetadata("Metadata must contain one Provider") + + spid = newsps.keys()[0] + return spid + + class ServiceProviderConfig(ConfigHelper): def __init__(self): super(ServiceProviderConfig, self).__init__() @@ -69,6 +101,11 @@ class ServiceProvider(ServiceProviderConfig): ' accepted.', self.name), pconfig.String( + 'Metadata', + "Service Provider's metadata", + self.metadata, + multiline=True), + pconfig.String( 'Description', 'A description of the SP to show on the Portal.', self.description), @@ -124,6 +161,17 @@ class ServiceProvider(ServiceProviderConfig): self._staging['name'] = value @property + def metadata(self): + if 'metadata' in self._properties: + return self._properties['metadata'] + else: + return '' + + @metadata.setter + def metadata(self, value): + self._staging['metadata'] = value + + @property def description(self): return self._properties.get('description', '') @@ -306,13 +354,7 @@ class ServiceProviderCreator(object): raise InvalidProviderId("Name must contain only " "numbers and letters") - test = lasso.Server() - test.addProviderFromBuffer(lasso.PROVIDER_ROLE_SP, metabuf) - newsps = test.get_providers() - if len(newsps) != 1: - raise InvalidProviderId("Metadata must contain one Provider") - - spid = newsps.keys()[0] + spid = validate_sp_metadata(metabuf) data = self.cfg.get_data(name='id', value=spid) if len(data) != 0: raise InvalidProviderId("Provider Already Exists") @@ -349,6 +391,7 @@ class IdentityProvider(Log): self.sessionfactory = sessionfactory def add_provider(self, sp): + validate_sp_metadata(sp['metadata']) self.server.addProviderFromBuffer(lasso.PROVIDER_ROLE_SP, sp['metadata']) self.debug('Added SP %s' % sp['name']) diff --git a/ipsilon/util/config.py b/ipsilon/util/config.py index e426679..61a4d75 100644 --- a/ipsilon/util/config.py +++ b/ipsilon/util/config.py @@ -148,9 +148,11 @@ class Option(Log): class String(Option): - def __init__(self, name, description, default_value=None, readonly=False): + def __init__(self, name, description, default_value=None, readonly=False, + multiline=False): super(String, self).__init__(name, description, readonly=readonly) self._default_value = str(default_value) + self.multiline = multiline def set_value(self, value): self._assigned_value = str(value) diff --git a/templates/admin/option_config.html b/templates/admin/option_config.html index 14326fd..7c7c52b 100644 --- a/templates/admin/option_config.html +++ b/templates/admin/option_config.html @@ -76,7 +76,9 @@
{% set value = v.get_value() -%} - {% if v.__class__.__name__ in ['String', 'Template'] -%} + {% if v.__class__.__name__ == 'String' and v.multiline -%} + + {% elif v.__class__.__name__ in ['String', 'Template'] -%}