diff --git a/etc/keystone.conf.sample b/etc/keystone.conf.sample index 08245b2342..d1879d8bb1 100644 --- a/etc/keystone.conf.sample +++ b/etc/keystone.conf.sample @@ -724,6 +724,10 @@ # Keystone Federation backend driver. (string value) #driver=keystone.contrib.federation.backends.sql.Federation +# Value to be used when filtering assertion parameters from +# the environment. (string value) +#assertion_prefix= + [identity] diff --git a/keystone/auth/plugins/saml2.py b/keystone/auth/plugins/saml2.py index d1fda08f5e..4daec75a49 100644 --- a/keystone/auth/plugins/saml2.py +++ b/keystone/auth/plugins/saml2.py @@ -71,7 +71,7 @@ class Saml2(auth.AuthMethodHandler): } def _handle_unscoped_token(self, context, auth_payload): - assertion = context['environment'] + assertion = dict(self._get_assertion_params_from_env(context)) identity_provider = auth_payload['identity_provider'] protocol = auth_payload['protocol'] @@ -104,3 +104,9 @@ class Saml2(auth.AuthMethodHandler): except exception.GroupNotFound: raise exception.MappedGroupNotFound( group_id=group_id, mapping_id=mapping_id) + + def _get_assertion_params_from_env(self, context): + prefix = CONF.federation.assertion_prefix + for k, v in context['environment'].items(): + if k.startswith(prefix): + yield (k, v) diff --git a/keystone/common/config.py b/keystone/common/config.py index 89a9b3cb7f..7bcd7dd288 100644 --- a/keystone/common/config.py +++ b/keystone/common/config.py @@ -359,7 +359,10 @@ FILE_OPTIONS = { cfg.StrOpt('driver', default='keystone.contrib.federation.' 'backends.sql.Federation', - help='Keystone Federation backend driver.')], + help='Keystone Federation backend driver.'), + cfg.StrOpt('assertion_prefix', default='', + help='Value to be used when filtering assertion parameters ' + 'from the environment.')], 'policy': [ cfg.StrOpt('driver', diff --git a/keystone/tests/mapping_fixtures.py b/keystone/tests/mapping_fixtures.py index 5cb9a0bd44..a07c4f53de 100644 --- a/keystone/tests/mapping_fixtures.py +++ b/keystone/tests/mapping_fixtures.py @@ -425,6 +425,14 @@ EMPLOYEE_ASSERTION = { 'orgPersonType': 'Employee;BuildingX;' } +EMPLOYEE_ASSERTION_PREFIXED = { + 'PREFIX_Email': 'tim@example.com', + 'PREFIX_UserName': 'tbo', + 'PREFIX_FirstName': 'Tim', + 'PREFIX_LastName': 'Bo', + 'PREFIX_orgPersonType': 'SuperEmployee;BuildingX;' +} + CONTRACTOR_ASSERTION = { 'Email': 'jill@example.com', 'UserName': 'jsmith', diff --git a/keystone/tests/test_v3_federation.py b/keystone/tests/test_v3_federation.py index 3a3c035a04..7b21d8621c 100644 --- a/keystone/tests/test_v3_federation.py +++ b/keystone/tests/test_v3_federation.py @@ -748,6 +748,7 @@ class FederatedTokenTests(FederationTests): PROTOCOL = 'saml2' AUTH_METHOD = 'saml2' USER = 'user@ORGANIZATION' + ASSERTION_PREFIX = 'PREFIX_' UNSCOPED_V3_SAML2_REQ = { "identity": { @@ -1142,6 +1143,37 @@ class FederatedTokenTests(FederationTests): body=scoped_token, expected_status=500) + def test_assertion_prefix_parameter(self): + """Test parameters filtering based on the prefix. + + With ``assertion_prefix`` set to fixed, non defailt value, + issue an unscoped token from assertion EMPLOYEE_ASSERTION_PREFIXED. + Expect server to return unscoped token. + + """ + self.config_fixture.config(group='federation', + assertion_prefix=self.ASSERTION_PREFIX) + r = self._issue_unscoped_token(assertion='EMPLOYEE_ASSERTION_PREFIXED') + self.assertIsNotNone(r.headers.get('X-Subject-Token')) + + def test_assertion_prefix_parameter_expect_fail(self): + """Test parameters filtering based on the prefix. + + With ``assertion_prefix`` default value set to empty string + issue an unscoped token from assertion EMPLOYEE_ASSERTION. + Next, configure ``assertion_prefix`` to value ``UserName``. + Try issuing unscoped token with EMPLOYEE_ASSERTION. + Expect server to raise exception.Unathorized exception. + + """ + r = self._issue_unscoped_token() + self.assertIsNotNone(r.headers.get('X-Subject-Token')) + self.config_fixture.config(group='federation', + assertion_prefix='UserName') + + self.assertRaises(exception.Unauthorized, + self._issue_unscoped_token) + def load_federation_sample_data(self): """Inject additional data.""" @@ -1292,6 +1324,31 @@ class FederatedTokenTests(FederationTests): } ] }, + { + 'local': [ + { + 'group': { + 'id': self.group_employees['id'] + } + }, + { + 'user': { + 'name': '{0}' + } + } + ], + 'remote': [ + { + 'type': self.ASSERTION_PREFIX + 'UserName' + }, + { + 'type': self.ASSERTION_PREFIX + 'orgPersonType', + 'any_one_of': [ + 'SuperEmployee' + ] + } + ] + }, { 'local': [ {