Unset project ids for all identity backends

Previously, the default behavior for the callback that unset
default project ids was to only call the method for the default
domain's identity driver. This meant that when a project was deleted,
only the default identity backend would have references to that
project removed. This means it would be possible for other identity
backends to still have references to a project that doesn't exist
because the callback wasn't invoked for that specific backend.

This commit ensures each backend clears project id from a user's
default_project_id attribute when a project is deleted.

Change-Id: Ibb5396f20101a3956fa91d6ff68155d4c00ab0f9
Closes-Bug: 1705072
This commit is contained in:
Lance Bragstad 2017-08-08 20:31:26 +00:00
parent 6167850d12
commit d0ad287df3
2 changed files with 28 additions and 9 deletions

View File

@ -15,6 +15,7 @@
"""Main entry point into the Identity service."""
import functools
import itertools
import operator
import os
import threading
@ -530,15 +531,19 @@ class Manager(manager.Manager):
"""
project_id = payload['resource_info']
try:
self.driver.unset_default_project_id(project_id)
except exception.Forbidden:
# NOTE(lbragstad): If the driver throws a Forbidden, it's because
# the driver doesn't support writes. This is the case with the
# in-tree LDAP implementation since it is read-only. This also
# ensures consistency for out-of-tree backends that might be
# read-only.
pass
drivers = itertools.chain(
self.domain_configs.values(), [{'driver': self.driver}]
)
for d in drivers:
try:
d['driver'].unset_default_project_id(project_id)
except exception.Forbidden:
# NOTE(lbragstad): If the driver throws a Forbidden, it's
# because the driver doesn't support writes. This is the case
# with the in-tree LDAP implementation since it is read-only.
# This also ensures consistency for out-of-tree backends that
# might be read-only.
pass
# Domain ID normalization methods
def _set_domain_id_and_mapping(self, ref, domain_id, driver,

View File

@ -33,6 +33,7 @@ from keystone import exception
from keystone import identity
from keystone.identity.backends import ldap as ldap_identity
from keystone.identity.backends.ldap import common as common_ldap
from keystone.identity.backends import sql as sql_identity
from keystone.identity.mapping_backends import mapping as map
from keystone.tests import unit
from keystone.tests.unit.assignment import test_backends as assignment_tests
@ -2500,6 +2501,19 @@ class MultiLDAPandSQLIdentity(BaseLDAPIdentity, unit.SQLDriverOverrides,
base = super(BaseLDAPIdentity, self)
base.test_remove_foreign_assignments_when_deleting_a_domain()
@mock.patch.object(ldap_identity.Identity, 'unset_default_project_id')
@mock.patch.object(sql_identity.Identity, 'unset_default_project_id')
def test_delete_project_unset_project_ids_for_all_backends(self, sql_mock,
ldap_mock):
ldap_mock.side_effect = exception.Forbidden
project = unit.new_project_ref(
domain_id=CONF.identity.default_domain_id
)
project = self.resource_api.create_project(project['id'], project)
self.resource_api.delete_project(project['id'])
ldap_mock.assert_called_with(project['id'])
sql_mock.assert_called_with(project['id'])
class MultiLDAPandSQLIdentityDomainConfigsInSQL(MultiLDAPandSQLIdentity):
"""Class to test the use of domain configs stored in the database.