diff --git a/keystone/contrib/federation/utils.py b/keystone/contrib/federation/utils.py index fae55a750e..14bdceb103 100644 --- a/keystone/contrib/federation/utils.py +++ b/keystone/contrib/federation/utils.py @@ -534,6 +534,13 @@ class RuleProcessor(object): return direct_maps + def _evaluate_values_by_regex(self, values, assertion_values): + for value in values: + for assertion_value in assertion_values: + if re.search(value, assertion_value): + return True + return False + def _evaluate_requirement(self, values, requirement_type, eval_type, regex, assertion): """Evaluate the incoming requirement and assertion. @@ -563,13 +570,10 @@ class RuleProcessor(object): return False if regex: - for value in values: - for assertion_value in assertion_values: - if re.search(value, assertion_value): - return True - return False - - any_match = bool(set(values).intersection(set(assertion_values))) + any_match = self._evaluate_values_by_regex(values, + assertion_values) + else: + any_match = bool(set(values).intersection(set(assertion_values))) if any_match and eval_type == self._EvalType.ANY_ONE_OF: return True if not any_match and eval_type == self._EvalType.NOT_ANY_OF: diff --git a/keystone/tests/mapping_fixtures.py b/keystone/tests/mapping_fixtures.py index aa254b26f4..81c39c2a84 100644 --- a/keystone/tests/mapping_fixtures.py +++ b/keystone/tests/mapping_fixtures.py @@ -458,6 +458,40 @@ MAPPING_TESTER_REGEX = { ] } +MAPPING_DEVELOPER_REGEX = { + "rules": [ + { + "local": [ + { + "user": { + "name": "{0}", + }, + "group": { + "id": DEVELOPER_GROUP_ID + } + } + ], + "remote": [ + { + "type": "UserName" + }, + { + "type": "orgPersonType", + "any_one_of": [ + "Developer" + ], + }, + { + "type": "Email", + "not_any_of": [ + ".*@example.org$" + ], + "regex": True + } + ] + } + ] +} MAPPING_GROUP_NAMES = { @@ -587,6 +621,14 @@ BAD_TESTER_ASSERTION = { 'orgPersonType': 'Tester;' } +BAD_DEVELOPER_ASSERTION = { + 'Email': 'evildeveloper@example.org', + 'UserName': 'Evil', + 'FirstName': 'Develop', + 'LastName': 'Account', + 'orgPersonType': 'Developer' +} + MALFORMED_TESTER_ASSERTION = { 'Email': 'testacct@example.com', 'UserName': 'testacct', @@ -598,6 +640,14 @@ MALFORMED_TESTER_ASSERTION = { 'tuple': tuple(xrange(5)) } +DEVELOPER_ASSERTION = { + 'Email': 'developacct@example.com', + 'UserName': 'developacct', + 'FirstName': 'Develop', + 'LastName': 'Account', + 'orgPersonType': 'Developer' +} + CONTRACTOR_MALFORMED_ASSERTION = { 'UserName': 'user', 'FirstName': object(), diff --git a/keystone/tests/test_v3_federation.py b/keystone/tests/test_v3_federation.py index eec5556992..fef5f2ec9b 100644 --- a/keystone/tests/test_v3_federation.py +++ b/keystone/tests/test_v3_federation.py @@ -707,6 +707,45 @@ class MappingRuleEngineTests(FederationTests): self.assertEqual(name, user_name) self.assertIn(mapping_fixtures.EMPLOYEE_GROUP_ID, group_ids) + def test_rule_engine_not_any_of_regex_verify_pass(self): + """Should return group DEVELOPER_GROUP_ID. + + The DEVELOPER_ASSERTION should successfully have a match in + MAPPING_DEVELOPER_REGEX. This will test the case where many + remote rules must be matched, including a `not_any_of`, with + regex set to True. + + """ + + mapping = mapping_fixtures.MAPPING_DEVELOPER_REGEX + assertion = mapping_fixtures.DEVELOPER_ASSERTION + rp = mapping_utils.RuleProcessor(mapping['rules']) + values = rp.process(assertion) + + user_name = assertion.get('UserName') + group_ids = values.get('group_ids') + name = values.get('name') + + self.assertEqual(user_name, name) + self.assertIn(mapping_fixtures.DEVELOPER_GROUP_ID, group_ids) + + def test_rule_engine_not_any_of_regex_verify_fail(self): + """Should deny authorization. + + The email in the assertion will fail the regex test. + It is set to reject any @example.org address, but the + incoming value is set to evildeveloper@example.org. + RuleProcessor should return list of empty group_ids. + + """ + + mapping = mapping_fixtures.MAPPING_DEVELOPER_REGEX + assertion = mapping_fixtures.BAD_DEVELOPER_ASSERTION + rp = mapping_utils.RuleProcessor(mapping['rules']) + mapped_properties = rp.process(assertion) + self.assertIsNone(mapped_properties['name']) + self.assertListEqual(list(), mapped_properties['group_ids']) + def _rule_engine_regex_match_and_many_groups(self, assertion): """Should return group DEVELOPER_GROUP_ID and TESTER_GROUP_ID.