Add support to create SAML assertion based on a token
A user should be able to exchange their token for a SAML assertion that is valid on a service provider (the user should must provide this data). implements bp generate-saml-assertions Change-Id: I5cb635929c7f6823ab1e4b1db5e48045be9e0737
This commit is contained in:
@@ -169,3 +169,87 @@ DOMAINS = {
|
||||
"next": 'null'
|
||||
}
|
||||
}
|
||||
|
||||
TOKEN_BASED_SAML = """
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<ns2:Response Destination="http://beta.example.com/Shibboleth.sso/POST/ECP"
|
||||
ID="8c21de08d2f2435c9acf13e72c982846"
|
||||
IssueInstant="2015-03-25T14:43:21Z"
|
||||
Version="2.0">
|
||||
<saml:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">
|
||||
http://keystone.idp/v3/OS-FEDERATION/saml2/idp
|
||||
</saml:Issuer>
|
||||
<ns2:Status>
|
||||
<ns2:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>
|
||||
</ns2:Status>
|
||||
<saml:Assertion ID="a5f02efb0bff4044b294b4583c7dfc5d"
|
||||
IssueInstant="2015-03-25T14:43:21Z" Version="2.0">
|
||||
<saml:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">
|
||||
http://keystone.idp/v3/OS-FEDERATION/saml2/idp</saml:Issuer>
|
||||
<xmldsig:Signature>
|
||||
<xmldsig:SignedInfo>
|
||||
<xmldsig:CanonicalizationMethod
|
||||
Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
|
||||
<xmldsig:SignatureMethod
|
||||
Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
|
||||
<xmldsig:Reference URI="#a5f02efb0bff4044b294b4583c7dfc5d">
|
||||
<xmldsig:Transforms>
|
||||
<xmldsig:Transform
|
||||
Algorithm="http://www.w3.org/2000/09/xmldsig#
|
||||
enveloped-signature"/>
|
||||
<xmldsig:Transform
|
||||
Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
|
||||
</xmldsig:Transforms>
|
||||
<xmldsig:DigestMethod
|
||||
Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
|
||||
<xmldsig:DigestValue>
|
||||
0KH2CxdkfzU+6eiRhTC+mbObUKI=
|
||||
</xmldsig:DigestValue>
|
||||
</xmldsig:Reference>
|
||||
</xmldsig:SignedInfo>
|
||||
<xmldsig:SignatureValue>
|
||||
m2jh5gDvX/1k+4uKtbb08CHp2b9UWsLw
|
||||
</xmldsig:SignatureValue>
|
||||
<xmldsig:KeyInfo>
|
||||
<xmldsig:X509Data>
|
||||
<xmldsig:X509Certificate>...</xmldsig:X509Certificate>
|
||||
</xmldsig:X509Data>
|
||||
</xmldsig:KeyInfo>
|
||||
</xmldsig:Signature>
|
||||
<saml:Subject>
|
||||
<saml:NameID>admin</saml:NameID>
|
||||
<saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
|
||||
<saml:SubjectConfirmationData
|
||||
NotOnOrAfter="2015-03-25T15:43:21.172385Z"
|
||||
Recipient="http://beta.example.com/Shibboleth.sso/POST/ECP"/>
|
||||
</saml:SubjectConfirmation>
|
||||
</saml:Subject>
|
||||
<saml:AuthnStatement AuthnInstant="2015-03-25T14:43:21Z"
|
||||
SessionIndex="9790eb729858456f8a33b7a11f0a637e"
|
||||
SessionNotOnOrAfter="2015-03-25T15:43:21.172385Z">
|
||||
<saml:AuthnContext>
|
||||
<saml:AuthnContextClassRef>
|
||||
urn:oasis:names:tc:SAML:2.0:ac:classes:Password
|
||||
</saml:AuthnContextClassRef>
|
||||
<saml:AuthenticatingAuthority>
|
||||
http://keystone.idp/v3/OS-FEDERATION/saml2/idp
|
||||
</saml:AuthenticatingAuthority>
|
||||
</saml:AuthnContext>
|
||||
</saml:AuthnStatement>
|
||||
<saml:AttributeStatement>
|
||||
<saml:Attribute Name="openstack_user"
|
||||
NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
|
||||
<saml:AttributeValue xsi:type="xs:string">admin</saml:AttributeValue>
|
||||
</saml:Attribute>
|
||||
<saml:Attribute Name="openstack_roles"
|
||||
NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
|
||||
<saml:AttributeValue xsi:type="xs:string">admin</saml:AttributeValue>
|
||||
</saml:Attribute>
|
||||
<saml:Attribute Name="openstack_project"
|
||||
NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
|
||||
<saml:AttributeValue xsi:type="xs:string">admin</saml:AttributeValue>
|
||||
</saml:Attribute>
|
||||
</saml:AttributeStatement>
|
||||
</saml:Assertion>
|
||||
</ns2:Response>
|
||||
"""
|
||||
|
@@ -25,6 +25,7 @@ from keystoneclient import session
|
||||
from keystoneclient.tests.unit.v3 import client_fixtures
|
||||
from keystoneclient.tests.unit.v3 import saml2_fixtures
|
||||
from keystoneclient.tests.unit.v3 import utils
|
||||
from keystoneclient.v3.contrib.federation import saml as saml_manager
|
||||
|
||||
ROOTDIR = os.path.dirname(os.path.abspath(__file__))
|
||||
XMLDIR = os.path.join(ROOTDIR, 'examples', 'xml/')
|
||||
@@ -621,3 +622,35 @@ class AuthenticateviaADFSTests(utils.TestCase):
|
||||
token, token_json = self.adfsplugin._get_unscoped_token(self.session)
|
||||
self.assertEqual(token, client_fixtures.AUTH_SUBJECT_TOKEN)
|
||||
self.assertEqual(saml2_fixtures.UNSCOPED_TOKEN['token'], token_json)
|
||||
|
||||
|
||||
class SAMLGenerationTests(utils.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(SAMLGenerationTests, self).setUp()
|
||||
self.manager = self.client.federation.saml
|
||||
self.SAML2_FULL_URL = ''.join([self.TEST_URL,
|
||||
saml_manager.SAML2_ENDPOINT])
|
||||
|
||||
def test_saml_create(self):
|
||||
"""Test that a token can be exchanged for a SAML assertion."""
|
||||
|
||||
token_id = uuid.uuid4().hex
|
||||
service_provider_id = uuid.uuid4().hex
|
||||
|
||||
# Mock the returned text for '/auth/OS-FEDERATION/saml2
|
||||
self.requests_mock.post(self.SAML2_FULL_URL,
|
||||
text=saml2_fixtures.TOKEN_BASED_SAML)
|
||||
|
||||
text = self.manager.create_saml_assertion(service_provider_id,
|
||||
token_id)
|
||||
|
||||
# Ensure returned text is correct
|
||||
self.assertEqual(saml2_fixtures.TOKEN_BASED_SAML, text)
|
||||
|
||||
# Ensure request headers and body are correct
|
||||
req_json = self.requests_mock.last_request.json()
|
||||
self.assertEqual(token_id, req_json['auth']['identity']['token']['id'])
|
||||
self.assertEqual(service_provider_id,
|
||||
req_json['auth']['scope']['service_provider']['id'])
|
||||
self.assertRequestHeaderEqual('Content-Type', 'application/json')
|
||||
|
@@ -15,6 +15,7 @@ from keystoneclient.v3.contrib.federation import identity_providers
|
||||
from keystoneclient.v3.contrib.federation import mappings
|
||||
from keystoneclient.v3.contrib.federation import projects
|
||||
from keystoneclient.v3.contrib.federation import protocols
|
||||
from keystoneclient.v3.contrib.federation import saml
|
||||
from keystoneclient.v3.contrib.federation import service_providers
|
||||
|
||||
|
||||
@@ -26,4 +27,5 @@ class FederationManager(object):
|
||||
self.protocols = protocols.ProtocolManager(api)
|
||||
self.projects = projects.ProjectManager(api)
|
||||
self.domains = domains.DomainManager(api)
|
||||
self.saml = saml.SamlManager(api)
|
||||
self.service_providers = service_providers.ServiceProviderManager(api)
|
||||
|
56
keystoneclient/v3/contrib/federation/saml.py
Normal file
56
keystoneclient/v3/contrib/federation/saml.py
Normal file
@@ -0,0 +1,56 @@
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from keystoneclient import base
|
||||
|
||||
|
||||
SAML2_ENDPOINT = '/auth/OS-FEDERATION/saml2'
|
||||
|
||||
|
||||
class SamlManager(base.Manager):
|
||||
"""Manager class for creating SAML assertions."""
|
||||
|
||||
def create_saml_assertion(self, service_provider, token_id):
|
||||
"""Create a SAML assertion from a token.
|
||||
|
||||
Equivalent Identity API call:
|
||||
POST /auth/OS-FEDERATION/saml2
|
||||
|
||||
:param service_provider: Service Provider resource.
|
||||
:type service_provider: string
|
||||
:param token_id: Token to transform to SAML assertion.
|
||||
:type token_id: string
|
||||
|
||||
:returns: SAML representation of token_id
|
||||
:rtype: string
|
||||
"""
|
||||
|
||||
body = {
|
||||
'auth': {
|
||||
'identity': {
|
||||
'methods': ['token'],
|
||||
'token': {
|
||||
'id': token_id
|
||||
}
|
||||
},
|
||||
'scope': {
|
||||
'service_provider': {
|
||||
'id': base.getid(service_provider)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
headers = {'Content-Type': 'application/json'}
|
||||
resp, body = self.client.post(SAML2_ENDPOINT, json=body,
|
||||
headers=headers)
|
||||
return resp.text
|
Reference in New Issue
Block a user