diff --git a/keystone/contrib/federation/idp.py b/keystone/contrib/federation/idp.py index c9c20c9102..49d6b67ed6 100644 --- a/keystone/contrib/federation/idp.py +++ b/keystone/contrib/federation/idp.py @@ -398,14 +398,25 @@ def _sign_assertion(assertion): '--id-attr:ID', 'Assertion'] try: - file_path = fileutils.write_to_tempfile(assertion.to_string()) + # NOTE(gyee): need to make the namespace prefixes explicit so + # they won't get reassigned when we wrap the assertion into + # SAML2 response + file_path = fileutils.write_to_tempfile(assertion.to_string( + nspair={'saml': saml2.NAMESPACE, + 'xmldsig': xmldsig.NAMESPACE})) command_list.append(file_path) - stdout = subprocess.check_output(command_list) - except Exception as e: - msg = _LE('Error when signing assertion, reason: %(reason)s') - msg = msg % {'reason': e} - LOG.error(msg) - raise exception.SAMLSigningError(reason=e) + process = subprocess.Popen(command_list, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + close_fds=True) + stdout, stderr = process.communicate() + retcode = process.poll() + if retcode: + msg = _LE('Error when signing assertion, reason: %(reason)s') + msg = msg % {'reason': stderr} + LOG.error(msg) + raise exception.SAMLSigningError(reason=stderr) finally: try: os.remove(file_path) diff --git a/keystone/tests/test_v3_federation.py b/keystone/tests/test_v3_federation.py index f06102123c..202e61c987 100644 --- a/keystone/tests/test_v3_federation.py +++ b/keystone/tests/test_v3_federation.py @@ -1803,6 +1803,37 @@ class SAMLGenerationTests(FederationTests): project_attribute = assertion[4][2] self.assertEqual(self.PROJECT, project_attribute[0].text) + def test_assertion_using_explicit_namespace_prefixes(self): + class MockedPopen(object): + def __init__(self, *popenargs, **kwargs): + # the last option is the assertion file to be signed + filename = popenargs[0][-1] + with open(filename, 'r') as f: + self.stdout = f.read() + + def communicate(self, *args, **kwargs): + # since we are not testing the signature itself, we can return + # the assertion as is without signing it + return (self.stdout, None) + + def poll(self): + return 0 + + with mock.patch('subprocess.Popen', + side_effect=MockedPopen): + generator = keystone_idp.SAMLGenerator() + response = generator.samlize_token(self.ISSUER, self.RECIPIENT, + self.SUBJECT, self.ROLES, + self.PROJECT) + assertion_xml = response.assertion.to_string() + # make sure we have the proper tag and prefix for the assertion + # namespace + self.assertIn('