summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRodrigo Duarte <rduartes@redhat.com>2016-03-17 14:28:55 -0300
committerRodrigo Duarte <rodrigodsousa@gmail.com>2016-05-05 12:32:57 +0000
commit9f04d7d8611bba611cdea3cb7cbab1af3c16bdc8 (patch)
tree07d90f56665303b5028bee723838db49e45c222a
parent61d6e404c768ab6124ac1c42bc5ae826f73d43ee (diff)
downloadkeystone-9f04d7d8611bba611cdea3cb7cbab1af3c16bdc8.tar.gz
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 bfcbb3cd7679dd13d5ededd2f3b765d40e0bca7d)
-rw-r--r--keystone/federation/V8_backends/sql.py39
-rw-r--r--keystone/federation/backends/sql.py42
-rw-r--r--keystone/tests/unit/test_v3_federation.py31
3 files changed, 86 insertions, 26 deletions
diff --git a/keystone/federation/V8_backends/sql.py b/keystone/federation/V8_backends/sql.py
index c95e9dbd2..d6b42aa0c 100644
--- a/keystone/federation/V8_backends/sql.py
+++ b/keystone/federation/V8_backends/sql.py
@@ -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):
diff --git a/keystone/federation/backends/sql.py b/keystone/federation/backends/sql.py
index 78a7831ba..add409e60 100644
--- a/keystone/federation/backends/sql.py
+++ b/keystone/federation/backends/sql.py
@@ -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):
diff --git a/keystone/tests/unit/test_v3_federation.py b/keystone/tests/unit/test_v3_federation.py
index e66fee707..f4ec8e517 100644
--- a/keystone/tests/unit/test_v3_federation.py
+++ b/keystone/tests/unit/test_v3_federation.py
@@ -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.