Merge "Add openstack_project_domain to assertion"
This commit is contained in:
commit
f029195f25
|
@ -346,9 +346,12 @@ class Auth(auth_controllers.Auth):
|
|||
raise exception.ForbiddenAction(action=action)
|
||||
|
||||
project = token_ref.project_name
|
||||
# NOTE(rodrigods): the domain name is necessary in order to distinguish
|
||||
# between projects with the same name in different domains.
|
||||
domain = token_ref.project_domain_name
|
||||
generator = keystone_idp.SAMLGenerator()
|
||||
response = generator.samlize_token(issuer, sp_url, subject, roles,
|
||||
project)
|
||||
project, domain)
|
||||
return (response, service_provider)
|
||||
|
||||
def _build_response_headers(self, service_provider):
|
||||
|
|
|
@ -44,7 +44,7 @@ class SAMLGenerator(object):
|
|||
self.assertion_id = uuid.uuid4().hex
|
||||
|
||||
def samlize_token(self, issuer, recipient, user, roles, project,
|
||||
expires_in=None):
|
||||
project_domain_name, expires_in=None):
|
||||
"""Convert Keystone attributes to a SAML assertion.
|
||||
|
||||
:param issuer: URL of the issuing party
|
||||
|
@ -57,6 +57,8 @@ class SAMLGenerator(object):
|
|||
:type roles: list
|
||||
:param project: Project name
|
||||
:type project: string
|
||||
:param project_domain_name: Project Domain name
|
||||
:type project_domain_name: string
|
||||
:param expires_in: Sets how long the assertion is valid for, in seconds
|
||||
:type expires_in: int
|
||||
|
||||
|
@ -67,8 +69,8 @@ class SAMLGenerator(object):
|
|||
status = self._create_status()
|
||||
saml_issuer = self._create_issuer(issuer)
|
||||
subject = self._create_subject(user, expiration_time, recipient)
|
||||
attribute_statement = self._create_attribute_statement(user, roles,
|
||||
project)
|
||||
attribute_statement = self._create_attribute_statement(
|
||||
user, roles, project, project_domain_name)
|
||||
authn_statement = self._create_authn_statement(issuer, expiration_time)
|
||||
signature = self._create_signature()
|
||||
|
||||
|
@ -153,7 +155,8 @@ class SAMLGenerator(object):
|
|||
subject.name_id = name_id
|
||||
return subject
|
||||
|
||||
def _create_attribute_statement(self, user, roles, project):
|
||||
def _create_attribute_statement(self, user, roles, project,
|
||||
project_domain_name):
|
||||
"""Create an object that represents a SAML AttributeStatement.
|
||||
|
||||
<ns0:AttributeStatement>
|
||||
|
@ -171,6 +174,10 @@ class SAMLGenerator(object):
|
|||
<ns0:AttributeValue
|
||||
xsi:type="xs:string">development</ns0:AttributeValue>
|
||||
</ns0:Attribute>
|
||||
<ns0:Attribute Name="openstack_project_domain">
|
||||
<ns0:AttributeValue
|
||||
xsi:type="xs:string">Default</ns0:AttributeValue>
|
||||
</ns0:Attribute>
|
||||
</ns0:AttributeStatement>
|
||||
|
||||
:return: XML <AttributeStatement> object
|
||||
|
@ -199,10 +206,18 @@ class SAMLGenerator(object):
|
|||
project_value.set_text(project)
|
||||
project_attribute.attribute_value = project_value
|
||||
|
||||
openstack_project_domain = 'openstack_project_domain'
|
||||
project_domain_attribute = saml.Attribute()
|
||||
project_domain_attribute.name = openstack_project_domain
|
||||
project_domain_value = saml.AttributeValue()
|
||||
project_domain_value.set_text(project_domain_name)
|
||||
project_domain_attribute.attribute_value = project_domain_value
|
||||
|
||||
attribute_statement = saml.AttributeStatement()
|
||||
attribute_statement.attribute.append(user_attribute)
|
||||
attribute_statement.attribute.append(roles_attribute)
|
||||
attribute_statement.attribute.append(project_attribute)
|
||||
attribute_statement.attribute.append(project_domain_attribute)
|
||||
return attribute_statement
|
||||
|
||||
def _create_authn_statement(self, issuer, expiration_time):
|
||||
|
|
|
@ -59,5 +59,8 @@ UHeBXxQq/GmfBv3l+V5ObQ+EHKnyDodLHCk=</ns1:X509Certificate>
|
|||
<ns0:Attribute Name="openstack_project" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
|
||||
<ns0:AttributeValue xsi:type="xs:string">development</ns0:AttributeValue>
|
||||
</ns0:Attribute>
|
||||
<ns0:Attribute Name="openstack_project_domain" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
|
||||
<ns0:AttributeValue xsi:type="xs:string">Default</ns0:AttributeValue>
|
||||
</ns0:Attribute>
|
||||
</ns0:AttributeStatement>
|
||||
</ns0:Assertion>
|
||||
|
|
|
@ -3018,6 +3018,7 @@ class SAMLGenerationTests(FederationTests):
|
|||
SUBJECT = 'test_user'
|
||||
ROLES = ['admin', 'member']
|
||||
PROJECT = 'development'
|
||||
DOMAIN = 'Default'
|
||||
SAML_GENERATION_ROUTE = '/auth/OS-FEDERATION/saml2'
|
||||
ECP_GENERATION_ROUTE = '/auth/OS-FEDERATION/saml2/ecp'
|
||||
ASSERTION_VERSION = "2.0"
|
||||
|
@ -3056,7 +3057,7 @@ class SAMLGenerationTests(FederationTests):
|
|||
generator = keystone_idp.SAMLGenerator()
|
||||
response = generator.samlize_token(self.ISSUER, self.RECIPIENT,
|
||||
self.SUBJECT, self.ROLES,
|
||||
self.PROJECT)
|
||||
self.PROJECT, self.DOMAIN)
|
||||
|
||||
assertion = response.assertion
|
||||
self.assertIsNotNone(assertion)
|
||||
|
@ -3076,6 +3077,11 @@ class SAMLGenerationTests(FederationTests):
|
|||
self.assertEqual(self.PROJECT,
|
||||
project_attribute.attribute_value[0].text)
|
||||
|
||||
project_domain_attribute = (
|
||||
assertion.attribute_statement[0].attribute[3])
|
||||
self.assertEqual(self.DOMAIN,
|
||||
project_domain_attribute.attribute_value[0].text)
|
||||
|
||||
def test_verify_assertion_object(self):
|
||||
"""Test that the Assertion object is built properly.
|
||||
|
||||
|
@ -3088,7 +3094,7 @@ class SAMLGenerationTests(FederationTests):
|
|||
generator = keystone_idp.SAMLGenerator()
|
||||
response = generator.samlize_token(self.ISSUER, self.RECIPIENT,
|
||||
self.SUBJECT, self.ROLES,
|
||||
self.PROJECT)
|
||||
self.PROJECT, self.DOMAIN)
|
||||
assertion = response.assertion
|
||||
self.assertEqual(self.ASSERTION_VERSION, assertion.version)
|
||||
|
||||
|
@ -3105,7 +3111,7 @@ class SAMLGenerationTests(FederationTests):
|
|||
generator = keystone_idp.SAMLGenerator()
|
||||
response = generator.samlize_token(self.ISSUER, self.RECIPIENT,
|
||||
self.SUBJECT, self.ROLES,
|
||||
self.PROJECT)
|
||||
self.PROJECT, self.DOMAIN)
|
||||
|
||||
saml_str = response.to_string()
|
||||
response = etree.fromstring(saml_str)
|
||||
|
@ -3125,6 +3131,9 @@ class SAMLGenerationTests(FederationTests):
|
|||
project_attribute = assertion[4][2]
|
||||
self.assertEqual(self.PROJECT, project_attribute[0].text)
|
||||
|
||||
project_domain_attribute = assertion[4][3]
|
||||
self.assertEqual(self.DOMAIN, project_domain_attribute[0].text)
|
||||
|
||||
def test_assertion_using_explicit_namespace_prefixes(self):
|
||||
def mocked_subprocess_check_output(*popenargs, **kwargs):
|
||||
# the last option is the assertion file to be signed
|
||||
|
@ -3140,7 +3149,7 @@ class SAMLGenerationTests(FederationTests):
|
|||
generator = keystone_idp.SAMLGenerator()
|
||||
response = generator.samlize_token(self.ISSUER, self.RECIPIENT,
|
||||
self.SUBJECT, self.ROLES,
|
||||
self.PROJECT)
|
||||
self.PROJECT, self.DOMAIN)
|
||||
assertion_xml = response.assertion.to_string()
|
||||
# make sure we have the proper tag and prefix for the assertion
|
||||
# namespace
|
||||
|
@ -3273,6 +3282,9 @@ class SAMLGenerationTests(FederationTests):
|
|||
project_attribute = assertion[4][2]
|
||||
self.assertIsInstance(project_attribute[0].text, str)
|
||||
|
||||
project_domain_attribute = assertion[4][3]
|
||||
self.assertIsInstance(project_domain_attribute[0].text, str)
|
||||
|
||||
def test_invalid_scope_body(self):
|
||||
"""Test that missing the scope in request body raises an exception.
|
||||
|
||||
|
@ -3382,6 +3394,9 @@ class SAMLGenerationTests(FederationTests):
|
|||
project_attribute = assertion[4][2]
|
||||
self.assertIsInstance(project_attribute[0].text, str)
|
||||
|
||||
project_domain_attribute = assertion[4][3]
|
||||
self.assertIsInstance(project_domain_attribute[0].text, str)
|
||||
|
||||
|
||||
class IdPMetadataGenerationTests(FederationTests):
|
||||
"""A class for testing Identity Provider Metadata generation."""
|
||||
|
|
Loading…
Reference in New Issue