From b304238c070f78e3ea3879768738a197cfe7c713 Mon Sep 17 00:00:00 2001 From: Marek Denis Date: Mon, 17 Mar 2014 11:49:54 +0100 Subject: [PATCH] Filter SAML2 assertion parameters with certain prefix. Add ``assertion_prefix`` option that filters environment parameters that will be passed to the RuleProcessor object. Parameters' names must start with ``assertion_prefix`` value. If not configured, ``assertion_prefix`` defaults to an empty string, and all environment parameters are passed to the RuleProcessor. Change-Id: I2696bbadcfff9745d8edca6c896c13fda49d636e Closes-Bug: #1293436 --- etc/keystone.conf.sample | 4 ++ keystone/auth/plugins/saml2.py | 8 +++- keystone/common/config.py | 5 ++- keystone/tests/mapping_fixtures.py | 8 ++++ keystone/tests/test_v3_federation.py | 57 ++++++++++++++++++++++++++++ 5 files changed, 80 insertions(+), 2 deletions(-) diff --git a/etc/keystone.conf.sample b/etc/keystone.conf.sample index 66a7c4432a..6891565604 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 c219fe164f..1a1149ecda 100644 --- a/keystone/auth/plugins/saml2.py +++ b/keystone/auth/plugins/saml2.py @@ -72,7 +72,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'] @@ -105,3 +105,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 ee1012fcc4..6839e79694 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 ec33f0b120..9371781471 100644 --- a/keystone/tests/test_v3_federation.py +++ b/keystone/tests/test_v3_federation.py @@ -747,6 +747,7 @@ class FederatedTokenTests(FederationTests): PROTOCOL = 'saml2' AUTH_METHOD = 'saml2' USER = 'user@ORGANIZATION' + ASSERTION_PREFIX = 'PREFIX_' UNSCOPED_V3_SAML2_REQ = { "identity": { @@ -1113,6 +1114,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.""" @@ -1263,6 +1295,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': [ {