Merge "Routes for Keystone-IdP metadata endpoint"

This commit is contained in:
Jenkins 2014-09-06 21:43:00 +00:00 committed by Gerrit Code Review
commit 8081eafffd
7 changed files with 87 additions and 5 deletions

View File

@ -1350,6 +1350,11 @@
# administrative billing, and other (string value)
#idp_contact_type=other
# Path to the Identity Provider Metadata file. This file
# should be generated with the keystone-manage
# saml_idp_metadata command. (string value)
#idp_metadata_path=/etc/keystone/saml2_idp_metadata.xml
[signing]

View File

@ -864,6 +864,11 @@ FILE_OPTIONS = {
help='Contact type. Allowed values are: '
'technical, support, administrative '
'billing, and other'),
cfg.StrOpt('idp_metadata_path',
default='/etc/keystone/saml2_idp_metadata.xml',
help='Path to the Identity Provider Metadata file. '
'This file should be generated with the '
'keystone-manage saml_idp_metadata command.'),
],
}

View File

@ -22,6 +22,7 @@ from keystone import config
from keystone.contrib.federation import idp as keystone_idp
from keystone.contrib.federation import schema
from keystone.contrib.federation import utils
from keystone import exception
from keystone.models import token_model
@ -332,3 +333,18 @@ class ProjectV3(controller.V3Controller):
projects = self.assignment_api.list_projects_for_groups(
auth_context['group_ids'])
return ProjectV3.wrap_collection(context, projects)
class SAMLMetadataV3(_ControllerBase):
member_name = 'metadata'
def get_metadata(self, context):
metadata_path = CONF.federation.idp_metadata_path
try:
with open(metadata_path, 'r') as metadata_handler:
metadata = metadata_handler.read()
except IOError as e:
# Raise HTTP 500 in case Metadata file cannot be read.
raise exception.MetadataFileError(reason=e)
return wsgi.render_response(body=metadata, status=('200', 'OK'),
headers=[('Content-Type', 'text/xml')])

View File

@ -67,8 +67,12 @@ class FederationExtension(wsgi.V3ExtensionRouter):
POST /OS-FEDERATION/identity_providers/$identity_provider/
protocols/$protocol/auth
POST /auth/OS-FEDERATION/saml2
GET /OS-FEDERATION/saml2/metadata
"""
def _construct_url(self, suffix):
return "/OS-FEDERATION/%s" % suffix
@ -83,6 +87,7 @@ class FederationExtension(wsgi.V3ExtensionRouter):
mapping_controller = controllers.MappingController()
project_controller = controllers.ProjectV3()
domain_controller = controllers.DomainV3()
saml_metadata_controller = controllers.SAMLMetadataV3()
# Identity Provider CRUD operations
@ -176,3 +181,10 @@ class FederationExtension(wsgi.V3ExtensionRouter):
path='/auth' + self._construct_url('saml2'),
post_action='create_saml_assertion',
rel=build_resource_relation(resource_name='saml2'))
# Keystone-Identity-Provider metadata endpoint
self._add_resource(
mapper, saml_metadata_controller,
path=self._construct_url('saml2/metadata'),
get_action='get_metadata',
rel=build_resource_relation(resource_name='metadata'))

View File

@ -375,6 +375,10 @@ class MappedGroupNotFound(UnexpectedError):
"%(mapping_id)s was not found in the backend.")
class MetadataFileError(UnexpectedError):
message_format = _("Error while reading metadata file, %(reason)s")
class NotImplemented(Error):
message_format = _("The action you have requested has not"
" been implemented.")

View File

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<ns0:EntityDescriptor xmlns:ns0="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:ns1="http://www.w3.org/2000/09/xmldsig#" entityID="k2k.com/v3/OS-FEDERATION/idp" validUntil="2014-08-19T21:24:17.411289Z">
<ns0:IDPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
<ns0:KeyDescriptor use="signing">
<ns1:KeyInfo>
<ns1:X509Data>
<ns1:X509Certificate>MIIDpTCCAo0CAREwDQYJKoZIhvcNAQEFBQAwgZ4xCjAIBgNVBAUTATUxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTESMBAGA1UEBxMJU3Vubnl2YWxlMRIwEAYDVQQKEwlPcGVuU3RhY2sxETAPBgNVBAsTCEtleXN0b25lMSUwIwYJKoZIhvcNAQkBFhZrZXlzdG9uZUBvcGVuc3RhY2sub3JnMRQwEgYDVQQDEwtTZWxmIFNpZ25lZDAgFw0xMzA3MDkxNjI1MDBaGA8yMDcyMDEwMTE2MjUwMFowgY8xCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTESMBAGA1UEBxMJU3Vubnl2YWxlMRIwEAYDVQQKEwlPcGVuU3RhY2sxETAPBgNVBAsTCEtleXN0b25lMSUwIwYJKoZIhvcNAQkBFhZrZXlzdG9uZUBvcGVuc3RhY2sub3JnMREwDwYDVQQDEwhLZXlzdG9uZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMTC6IdNd9Cg1DshcrT5gRVRF36nEmjSA9QWdik7B925PK70U4F6j4pz/5JL7plIo/8rJ4jJz9ccE7m0iA+IuABtEhEwXkG9rj47Oy0J4ZyDGSh2K1Bl78PA9zxXSzysUTSjBKdAh29dPYbJY7cgZJ0uC3AtfVceYiAOIi14SdFeZ0LZLDXBuLaqUmSMrmKwJ9wAMOCb/jbBP9/3Ycd0GYjlvrSBU4Bqb8/NHasyO4DpPN68OAoyD5r5jUtV8QZN03UjIsoux8e0lrL6+MVtJo0OfWvlSrlzS5HKSryY+uqqQEuxtZKpJM2MV85ujvjc8eDSChh2shhDjBem3FIlHKUCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAed9fHgdJrk+gZcO5gsqq6uURfDOuYD66GsSdZw4BqHjYAcnyWq2da+iw7Uxkqu7iLf2k4+Hu3xjDFrce479OwZkSnbXmqB7XspTGOuM8MgT7jB/ypKTOZ6qaZKSWK1Hta995hMrVVlhUNBLh0MPGqoVWYA4d7mblujgH9vp+4mpCciJagHks8K5FBmI+pobB+uFdSYDoRzX9LTpStspK4e3IoY8baILuGcdKimRNBv6ItG4hMrntAe1/nWMJyUu5rDTGf2V/vAaS0S/faJBwQSz1o38QHMTWHNspfwIdX3yMqI9u7/vYlz3rLy5WdBdUgZrZ3/VLmJTiJVZu5Owq4Q==
</ns1:X509Certificate>
</ns1:X509Data>
</ns1:KeyInfo>
</ns0:KeyDescriptor>
</ns0:IDPSSODescriptor>
<ns0:Organization>
<ns0:OrganizationName xml:lang="en">openstack</ns0:OrganizationName>
<ns0:OrganizationDisplayName xml:lang="en">openstack</ns0:OrganizationDisplayName>
<ns0:OrganizationURL xml:lang="en">openstack</ns0:OrganizationURL>
</ns0:Organization>
<ns0:ContactPerson contactType="technical">
<ns0:Company>openstack</ns0:Company>
<ns0:GivenName>first</ns0:GivenName>
<ns0:SurName>lastname</ns0:SurName>
<ns0:EmailAddress>admin@example.com</ns0:EmailAddress>
<ns0:TelephoneNumber>555-555-5555</ns0:TelephoneNumber>
</ns0:ContactPerson>
</ns0:EntityDescriptor>

View File

@ -1652,6 +1652,11 @@ def _is_xmlsec1_installed():
return not bool(p.wait())
def _load_xml(filename):
with open(os.path.join(XMLDIR, filename), 'r') as xml:
return xml.read()
class SAMLGenerationTests(FederationTests):
ISSUER = 'https://acme.com/FIM/sps/openstack/saml20'
@ -1664,11 +1669,7 @@ class SAMLGenerationTests(FederationTests):
def setUp(self):
super(SAMLGenerationTests, self).setUp()
self.signed_assertion = saml2.create_class_from_xml_string(
saml.Assertion, self._load_xml('signed_saml2_assertion.xml'))
def _load_xml(self, filename):
with open(os.path.join(XMLDIR, filename), 'r') as xml:
return xml.read()
saml.Assertion, _load_xml('signed_saml2_assertion.xml'))
def test_samlize_token_values(self):
"""Test the SAML generator produces a SAML object.
@ -1901,6 +1902,8 @@ class SAMLGenerationTests(FederationTests):
class IdPMetadataGenerationTests(FederationTests):
"""A class for testing Identity Provider Metadata generation."""
METADATA_URL = '/OS-FEDERATION/saml2/metadata'
def setUp(self):
super(IdPMetadataGenerationTests, self).setUp()
self.generator = keystone_idp.MetadataGenerator()
@ -2011,3 +2014,15 @@ class IdPMetadataGenerationTests(FederationTests):
idp_entity_id=None)
self.assertRaises(exception.ValidationError,
self.generator.generate_metadata)
def test_get_metadata_with_no_metadata_file_configured(self):
self.get(self.METADATA_URL, expected_status=500)
def test_get_metadata(self):
CONF.federation.idp_metadata_path = XMLDIR + '/idp_saml2_metadata.xml'
r = self.get(self.METADATA_URL, response_content_type='text/xml',
expected_status=200)
self.assertEqual('text/xml', r.headers.get('Content-Type'))
reference_file = _load_xml('idp_saml2_metadata.xml')
self.assertEqual(reference_file, r.result)