Skip to content

Commit d4c4a47

Browse files
committed
Fix issue2550954: History display breaks
.. on removed properties: Now changes to removed properties, and link/unlink events from non-existing properties or classes no longer trigger a traceback. Concerning the visibility: We have a new config-item obsolete_history_roles in the main section that defines which roles may see removed properties. By default only role Admin is allowed to see these.
1 parent 079dc92 commit d4c4a47

File tree

4 files changed

+79
-4
lines changed

4 files changed

+79
-4
lines changed

CHANGES.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -480,6 +480,13 @@ Fixed:
480480
Bcc and cc users passed to nosymessage are not properly recorded.
481481
This results in duplicate emails. (patch by Trent Gamblin (trentgg)
482482
applied by John Rouillard).
483+
- issue2550954: History display breaks on removed properties
484+
Now changes to removed properties, and link/unlink events from
485+
non-existing properties or classes no longer trigger a traceback.
486+
Concerning the visibility: We have a new config-item
487+
obsolete_history_roles in the main section that defines which roles
488+
may see removed properties. By default only role Admin is allowed to
489+
see these.
483490

484491
2016-01-11: 1.5.1
485492

roundup/configuration.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -605,6 +605,14 @@ def str2value(self, value):
605605
" with Email Gateway.\n"
606606
"This is a comma-separated string of role names"
607607
" (e.g. 'Admin,User')."),
608+
(Option, "obsolete_history_roles", "Admin",
609+
"On schema changes, properties or classes in the history may\n"
610+
"become obsolete. Since normal access permissions do not apply\n"
611+
"(we don't know if a user should see such a property or class)\n"
612+
"a list of roles is specified here that are allowed to see\n"
613+
"these obsolete properties in the history. By default only the\n"
614+
"admin role may see these history entries, you can make them\n"
615+
"visible to all users by adding, e.g., the 'User' role here."),
608616
(Option, "error_messages_to", "user",
609617
# XXX This description needs better wording,
610618
# with explicit allowed values list.

roundup/hyperdb.py

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1015,6 +1015,10 @@ def history(self, nodeid, enforceperm=True, skipquiet=True):
10151015
If the user requesting the history does not have View access
10161016
to the property, the journal entry will not be shown. This can
10171017
be disabled by setting enforceperm=False.
1018+
1019+
Note that there is a check for obsolete properties and classes
1020+
resulting from history changes. These are also only checked if
1021+
enforceperm is True.
10181022
"""
10191023
if not self.do_journal:
10201024
raise ValueError('Journalling is disabled for this class')
@@ -1024,17 +1028,27 @@ def history(self, nodeid, enforceperm=True, skipquiet=True):
10241028

10251029
uid=self.db.getuid() # id of the person requesting the history
10261030

1031+
# Roles of the user and the configured obsolete_history_roles
1032+
hr = set(iter_roles(self.db.config.OBSOLETE_HISTORY_ROLES))
1033+
ur = set(self.db.user.get_roles(uid))
1034+
allow_obsolete = bool(hr & ur)
1035+
10271036
for j in self.db.getjournal(self.classname, nodeid):
10281037
# hide/remove journal entry if:
10291038
# property is quiet
10301039
# property is not (viewable or editable)
1040+
# property is obsolete and not allow_obsolete
10311041
id, evt_date, user, action, args = j
10321042
if logger.isEnabledFor(logging.DEBUG):
10331043
j_repr = "%s"%(j,)
10341044
else:
10351045
j_repr=''
10361046
if args and type(args) == type({}):
10371047
for key in args.keys():
1048+
if key not in self.properties :
1049+
if enforceperm and not allow_obsolete:
1050+
del j[4][key]
1051+
continue
10381052
if skipquiet and self.properties[key].quiet:
10391053
logger.debug("skipping quiet property"
10401054
" %s::%s in %s",
@@ -1048,7 +1062,8 @@ def history(self, nodeid, enforceperm=True, skipquiet=True):
10481062
uid,
10491063
self.classname,
10501064
property=key )):
1051-
logger.debug("skipping unaccessible property %s::%s seen by user%s in %s",
1065+
logger.debug("skipping unaccessible property "
1066+
"%s::%s seen by user%s in %s",
10521067
self.classname, key, uid, j_repr)
10531068
del j[4][key]
10541069
continue
@@ -1078,7 +1093,16 @@ def history(self, nodeid, enforceperm=True, skipquiet=True):
10781093
# j = id, evt_date, user, action, args
10791094
# 3|20170528045201.484|5|link|('issue', '5', 'blockedby')
10801095
linkcl, linkid, key = args
1081-
cls = self.db.getclass(linkcl)
1096+
cls = None
1097+
try:
1098+
cls = self.db.getclass(linkcl)
1099+
except KeyError:
1100+
pass
1101+
# obsolete property or class
1102+
if not cls or key not in cls.properties:
1103+
if not enforceperm or allow_obsolete:
1104+
journal.append(j)
1105+
continue
10821106
# is the updated property quiet?
10831107
if skipquiet and cls.properties[key].quiet:
10841108
logger.debug("skipping quiet property: "

test/db_test_base.py

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -145,8 +145,8 @@ def tearDown(self):
145145
if os.path.exists(config.DATABASE):
146146
shutil.rmtree(config.DATABASE)
147147

148-
def open_database(self):
149-
self.db = self.module.Database(config, 'admin')
148+
def open_database(self, user='admin'):
149+
self.db = self.module.Database(config, user)
150150

151151

152152
if os.environ.has_key('LOGGING_LEVEL'):
@@ -1275,6 +1275,42 @@ def testJournals(self):
12751275
# see if the change was journalled
12761276
self.assertNotEqual(jlen, len(self.db.getjournal('issue', '1')))
12771277

1278+
def testJournalNonexistingProperty(self):
1279+
# Test for non-existing properties, link/unlink events to
1280+
# non-existing classes and link/unlink events to non-existing
1281+
# properties in a class: These all may be the result of a schema
1282+
# change and should not lead to a traceback.
1283+
self.db.user.create(username="mary")
1284+
id = self.db.issue.create(title="spam", status='1')
1285+
self.db.commit()
1286+
journal = self.db.getjournal('issue', id)
1287+
now = date.Date('.')
1288+
sec = date.Interval('0:00:01')
1289+
sec2 = date.Interval('0:00:02')
1290+
# Non-existing property changed
1291+
jp1 = dict(nonexisting = None)
1292+
journal.append ((id, now, '1', 'set', jp1))
1293+
# Link from user-class to non-existing property
1294+
jp2 = ('user', '1', 'xyzzy')
1295+
journal.append ((id, now+sec, '1', 'link', jp2))
1296+
# Link from non-existing class
1297+
jp3 = ('frobozz', '1', 'xyzzy')
1298+
journal.append ((id, now+sec2, '1', 'link', jp3))
1299+
self.db.setjournal('issue', id, journal)
1300+
self.db.commit()
1301+
result=self.db.issue.history(id)
1302+
result.sort()
1303+
self.assertEqual(len(result), 4)
1304+
self.assertEqual(result [1][4], jp1)
1305+
self.assertEqual(result [2][4], jp2)
1306+
self.assertEqual(result [3][4], jp3)
1307+
self.db.close()
1308+
# Verify that normal user doesn't see obsolete props/classes
1309+
self.open_database('mary')
1310+
setupSchema(self.db, 0, self.module)
1311+
result=self.db.issue.history(id)
1312+
self.assertEqual(len(result), 1)
1313+
12781314
def testJournalPreCommit(self):
12791315
id = self.db.user.create(username="mary")
12801316
self.assertEqual(len(self.db.getjournal('user', id)), 1)

0 commit comments

Comments
 (0)