fix security issue #36564 (integrity error w/ RQLUniqueConstraint) by using a global lock serializing commit when some RQLUniqueConstraint is encountered.
--- a/server/hooks.py Mon Feb 08 09:57:25 2010 +0100
+++ b/server/hooks.py Mon Feb 08 10:06:40 2010 +0100
@@ -8,6 +8,7 @@
"""
__docformat__ = "restructuredtext en"
+from threading import Lock
from datetime import datetime
from cubicweb import UnknownProperty, ValidationError, BadConnectionId
@@ -25,6 +26,41 @@
DONT_CHECK_RTYPES_ON_DEL = set(('is', 'is_instance_of',
'wf_info_for', 'from_state', 'to_state'))
+_UNIQUE_CONSTRAINTS_LOCK = Lock()
+_UNIQUE_CONSTRAINTS_HOLDER = None
+
+class _ReleaseUniqueConstraintsHook(Operation):
+ def commit_event(self):
+ pass
+ def postcommit_event(self):
+ _release_unique_cstr_lock(self.session)
+ def rollback_event(self):
+ _release_unique_cstr_lock(self.session)
+
+def _acquire_unique_cstr_lock(session):
+ """acquire the _UNIQUE_CONSTRAINTS_LOCK for the session.
+
+ This lock used to avoid potential integrity pb when checking
+ RQLUniqueConstraint in two different transactions, as explained in
+ http://intranet.logilab.fr/jpl/ticket/36564
+ """
+ global _UNIQUE_CONSTRAINTS_HOLDER
+ asession = session.actual_session()
+ if _UNIQUE_CONSTRAINTS_HOLDER is asession:
+ return
+ _UNIQUE_CONSTRAINTS_LOCK.acquire()
+ _UNIQUE_CONSTRAINTS_HOLDER = asession
+ # register operation responsible to release the lock on commit/rollback
+ _ReleaseUniqueConstraintsHook(asession)
+
+def _release_unique_cstr_lock(session):
+ global _UNIQUE_CONSTRAINTS_HOLDER
+ if _UNIQUE_CONSTRAINTS_HOLDER is session:
+ _UNIQUE_CONSTRAINTS_HOLDER = None
+ _UNIQUE_CONSTRAINTS_LOCK.release()
+ else:
+ assert _UNIQUE_CONSTRAINTS_HOLDER is None
+
def relation_deleted(session, eidfrom, rtype, eidto):
session.transaction_data.setdefault('pendingrelations', []).append(
@@ -216,6 +252,12 @@
if eidto in pending:
return
for constraint in self.constraints:
+ # XXX
+ # * lock RQLConstraint as well?
+ # * use a constraint id to use per constraint lock and avoid
+ # unnecessary commit serialization ?
+ if isinstance(constraint, RQLUniqueConstraint):
+ _acquire_unique_cstr_lock(self.session)
try:
constraint.repo_check(self.session, eidfrom, rtype, eidto)
except NotImplementedError: