Add conflict validation for idp update
Remote IDs conflicts can happen during an identity provider
update (similar to what happens during create).
This patch adds the same conflict handling, so a 500 is not
returned by keystone.
Change-Id: I1f093dad0b9427027edf4dc1a9f563e99aedad0c
Closes-Bug: 1558670
(cherry picked from commit bfcbb3cd76
)
This commit is contained in:
parent
61d6e404c7
commit
9f04d7d861
|
@ -12,12 +12,18 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from oslo_log import log
|
||||
from oslo_serialization import jsonutils
|
||||
import six
|
||||
from sqlalchemy import orm
|
||||
|
||||
from keystone.common import sql
|
||||
from keystone import exception
|
||||
from keystone.federation import core
|
||||
from keystone.i18n import _
|
||||
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
class FederationProtocolModel(sql.ModelBase, sql.DictBase):
|
||||
|
@ -157,6 +163,20 @@ class ServiceProviderModel(sql.ModelBase, sql.DictBase):
|
|||
|
||||
class Federation(core.FederationDriverV8):
|
||||
|
||||
_CONFLICT_LOG_MSG = 'Conflict %(conflict_type)s: %(details)s'
|
||||
|
||||
def _handle_idp_conflict(self, e):
|
||||
conflict_type = 'identity_provider'
|
||||
details = six.text_type(e)
|
||||
LOG.debug(self._CONFLICT_LOG_MSG, {'conflict_type': conflict_type,
|
||||
'details': details})
|
||||
if 'remote_id' in details:
|
||||
msg = _('Duplicate remote ID: %s')
|
||||
else:
|
||||
msg = _('Duplicate entry: %s')
|
||||
msg = msg % e.value
|
||||
raise exception.Conflict(type=conflict_type, details=msg)
|
||||
|
||||
# Identity Provider CRUD
|
||||
@sql.handle_conflicts(conflict_type='identity_provider')
|
||||
def create_idp(self, idp_id, idp):
|
||||
|
@ -203,14 +223,17 @@ class Federation(core.FederationDriverV8):
|
|||
return ref.to_dict()
|
||||
|
||||
def update_idp(self, idp_id, idp):
|
||||
with sql.session_for_write() as session:
|
||||
idp_ref = self._get_idp(session, idp_id)
|
||||
old_idp = idp_ref.to_dict()
|
||||
old_idp.update(idp)
|
||||
new_idp = IdentityProviderModel.from_dict(old_idp)
|
||||
for attr in IdentityProviderModel.mutable_attributes:
|
||||
setattr(idp_ref, attr, getattr(new_idp, attr))
|
||||
return idp_ref.to_dict()
|
||||
try:
|
||||
with sql.session_for_write() as session:
|
||||
idp_ref = self._get_idp(session, idp_id)
|
||||
old_idp = idp_ref.to_dict()
|
||||
old_idp.update(idp)
|
||||
new_idp = IdentityProviderModel.from_dict(old_idp)
|
||||
for attr in IdentityProviderModel.mutable_attributes:
|
||||
setattr(idp_ref, attr, getattr(new_idp, attr))
|
||||
return idp_ref.to_dict()
|
||||
except sql.DBDuplicateEntry as e:
|
||||
self._handle_idp_conflict(e)
|
||||
|
||||
# Protocol CRUD
|
||||
def _get_protocol(self, session, idp_id, protocol_id):
|
||||
|
|
|
@ -165,6 +165,18 @@ class Federation(core.FederationDriverV9):
|
|||
|
||||
_CONFLICT_LOG_MSG = 'Conflict %(conflict_type)s: %(details)s'
|
||||
|
||||
def _handle_idp_conflict(self, e):
|
||||
conflict_type = 'identity_provider'
|
||||
details = six.text_type(e)
|
||||
LOG.debug(self._CONFLICT_LOG_MSG, {'conflict_type': conflict_type,
|
||||
'details': details})
|
||||
if 'remote_id' in details:
|
||||
msg = _('Duplicate remote ID: %s')
|
||||
else:
|
||||
msg = _('Duplicate entry: %s')
|
||||
msg = msg % e.value
|
||||
raise exception.Conflict(type=conflict_type, details=msg)
|
||||
|
||||
# Identity Provider CRUD
|
||||
def create_idp(self, idp_id, idp):
|
||||
idp['id'] = idp_id
|
||||
|
@ -174,16 +186,7 @@ class Federation(core.FederationDriverV9):
|
|||
session.add(idp_ref)
|
||||
return idp_ref.to_dict()
|
||||
except sql.DBDuplicateEntry as e:
|
||||
conflict_type = 'identity_provider'
|
||||
details = six.text_type(e)
|
||||
LOG.debug(self._CONFLICT_LOG_MSG, {'conflict_type': conflict_type,
|
||||
'details': details})
|
||||
if 'remote_id' in details:
|
||||
msg = _('Duplicate remote ID: %s')
|
||||
else:
|
||||
msg = _('Duplicate entry: %s')
|
||||
msg = msg % e.value
|
||||
raise exception.Conflict(type=conflict_type, details=msg)
|
||||
self._handle_idp_conflict(e)
|
||||
|
||||
def delete_idp(self, idp_id):
|
||||
with sql.session_for_write() as session:
|
||||
|
@ -223,14 +226,17 @@ class Federation(core.FederationDriverV9):
|
|||
return ref.to_dict()
|
||||
|
||||
def update_idp(self, idp_id, idp):
|
||||
with sql.session_for_write() as session:
|
||||
idp_ref = self._get_idp(session, idp_id)
|
||||
old_idp = idp_ref.to_dict()
|
||||
old_idp.update(idp)
|
||||
new_idp = IdentityProviderModel.from_dict(old_idp)
|
||||
for attr in IdentityProviderModel.mutable_attributes:
|
||||
setattr(idp_ref, attr, getattr(new_idp, attr))
|
||||
return idp_ref.to_dict()
|
||||
try:
|
||||
with sql.session_for_write() as session:
|
||||
idp_ref = self._get_idp(session, idp_id)
|
||||
old_idp = idp_ref.to_dict()
|
||||
old_idp.update(idp)
|
||||
new_idp = IdentityProviderModel.from_dict(old_idp)
|
||||
for attr in IdentityProviderModel.mutable_attributes:
|
||||
setattr(idp_ref, attr, getattr(new_idp, attr))
|
||||
return idp_ref.to_dict()
|
||||
except sql.DBDuplicateEntry as e:
|
||||
self._handle_idp_conflict(e)
|
||||
|
||||
# Protocol CRUD
|
||||
def _get_protocol(self, session, idp_id, protocol_id):
|
||||
|
|
|
@ -988,6 +988,37 @@ class FederatedIdentityProviderTests(test_v3.RestfulTestCase):
|
|||
self.assertEqual(sorted(body['remote_ids']),
|
||||
sorted(returned_idp.get('remote_ids')))
|
||||
|
||||
def test_update_idp_remote_repeated(self):
|
||||
"""Update an IdentityProvider entity reusing a remote_id.
|
||||
|
||||
A remote_id is the same for both so the second IdP is not
|
||||
updated because of the uniqueness of the remote_ids.
|
||||
|
||||
Expect HTTP 409 Conflict code for the latter call.
|
||||
|
||||
"""
|
||||
# Create first identity provider
|
||||
body = self.default_body.copy()
|
||||
repeated_remote_id = uuid.uuid4().hex
|
||||
body['remote_ids'] = [uuid.uuid4().hex,
|
||||
repeated_remote_id]
|
||||
self._create_default_idp(body=body)
|
||||
|
||||
# Create second identity provider (without remote_ids)
|
||||
body = self.default_body.copy()
|
||||
default_resp = self._create_default_idp(body=body)
|
||||
default_idp = self._fetch_attribute_from_response(default_resp,
|
||||
'identity_provider')
|
||||
idp_id = default_idp.get('id')
|
||||
url = self.base_url(suffix=idp_id)
|
||||
|
||||
body['remote_ids'] = [repeated_remote_id]
|
||||
resp = self.patch(url, body={'identity_provider': body},
|
||||
expected_status=http_client.CONFLICT)
|
||||
resp_data = jsonutils.loads(resp.body)
|
||||
self.assertIn('Duplicate remote ID',
|
||||
resp_data['error']['message'])
|
||||
|
||||
def test_list_idps(self, iterations=5):
|
||||
"""Lists all available IdentityProviders.
|
||||
|
||||
|
|
Loading…
Reference in New Issue