diff --git a/keystone/assignment/core.py b/keystone/assignment/core.py index 508e181b55..c602c78676 100644 --- a/keystone/assignment/core.py +++ b/keystone/assignment/core.py @@ -339,11 +339,19 @@ class Manager(manager.Manager): def create_grant(self, role_id, user_id=None, group_id=None, domain_id=None, project_id=None, inherited_to_projects=False, context=None): - self.role_api.get_role(role_id) + role = self.role_api.get_role(role_id) if domain_id: self.resource_api.get_domain(domain_id) if project_id: - self.resource_api.get_project(project_id) + project = self.resource_api.get_project(project_id) + + # For domain specific roles, the domain of the project must match + # the roles's + if role['domain_id'] and project['domain_id'] != role['domain_id']: + raise exception.DomainSpecificRoleMismatch( + role_id=role_id, + project_id=project_id) + self.driver.create_grant(role_id, user_id, group_id, domain_id, project_id, inherited_to_projects) COMPUTED_ASSIGNMENTS_REGION.invalidate() diff --git a/keystone/exception.py b/keystone/exception.py index 568f567485..d941f23eea 100644 --- a/keystone/exception.py +++ b/keystone/exception.py @@ -350,6 +350,11 @@ class InvalidImpliedRole(Forbidden): message_format = _("%(role_id)s cannot be an implied roles") +class DomainSpecificRoleMismatch(Forbidden): + message_format = _("project: %(project_id)s must be in the same domain " + " as the role: %(role_id)s being assigned.") + + class RoleAssignmentNotFound(NotFound): message_format = _("Could not find role assignment with role: " "%(role_id)s, user or group: %(actor_id)s, " diff --git a/keystone/tests/unit/test_v3_assignment.py b/keystone/tests/unit/test_v3_assignment.py index 30b54061a8..565eb37fcb 100644 --- a/keystone/tests/unit/test_v3_assignment.py +++ b/keystone/tests/unit/test_v3_assignment.py @@ -20,6 +20,7 @@ from six.moves import range from testtools import matchers import keystone.conf +from keystone import exception from keystone.tests import unit from keystone.tests.unit import test_v3 @@ -2820,6 +2821,45 @@ class DomainSpecificRoleTests(test_v3.RestfulTestCase, unit.TestCase): self.assertValidRoleListResponse(r, expected_length=1) self.assertRoleInListResponse(r, self.domainA_role2) + def test_same_domain_assignment(self): + user = unit.create_user(self.identity_api, + domain_id=self.domainA['id']) + + projectA = unit.new_project_ref(domain_id=self.domainA['id']) + self.resource_api.create_project(projectA['id'], projectA) + + self.assignment_api.create_grant(self.domainA_role1['id'], + user_id=user['id'], + project_id=projectA['id']) + + def test_cross_domain_assignment_valid(self): + user = unit.create_user(self.identity_api, + domain_id=self.domainB['id']) + + projectA = unit.new_project_ref(domain_id=self.domainA['id']) + self.resource_api.create_project(projectA['id'], projectA) + + # Positive: a role on domainA can be assigned to a user from domainB + # but only for use on a project from domainA + self.assignment_api.create_grant(self.domainA_role1['id'], + user_id=user['id'], + project_id=projectA['id']) + + def test_cross_domain_assignment_invalid(self): + user = unit.create_user(self.identity_api, + domain_id=self.domainB['id']) + + projectB = unit.new_project_ref(domain_id=self.domainB['id']) + self.resource_api.create_project(projectB['id'], projectB) + + # Negative: a role on domainA can be assigned to a user from domainB + # only for a project from domainA + self.assertRaises(exception.DomainSpecificRoleMismatch, + self.assignment_api.create_grant, + self.domainA_role1['id'], + user_id=user['id'], + project_id=projectB['id']) + class ListUserProjectsTestCase(test_v3.RestfulTestCase): """Test for /users//projects.""" diff --git a/releasenotes/notes/bug-1590587-domain-specific-role-assignment-8f120604a6625852.yaml b/releasenotes/notes/bug-1590587-domain-specific-role-assignment-8f120604a6625852.yaml new file mode 100644 index 0000000000..bcda62676a --- /dev/null +++ b/releasenotes/notes/bug-1590587-domain-specific-role-assignment-8f120604a6625852.yaml @@ -0,0 +1,7 @@ +--- +fixes: + - > + [`bug 1590587 `_] + When assigning Domain Specific Roles, the domain of the role and the + domain of the project must match. This is now validated and the REST + call will return a 403 Forbidden.