Mercurial > p > roundup > code
comparison roundup/hyperdb.py @ 5232:462b0f76fce8
issue2550864 - Potential information leakage via journal/history
Fix this by making the hyperdb::Class::history function check for view
permissions on the journaled properties. So a user that sees [hidden]
for a property in the web interface doesn;t see the property changes
in the history.
While doing this, relocated the filter for quiet properties
from the templating class to the hyperdb.
Also added the skipquiet option to the history command in
roundup-admin.py to enable filtering of quiet params.
Also changed calls to history() in the backend databases to report all
items.
Changed inline documentation for all history calls that document the
actions. The create action (before nov 6 2002) used to record all
parameters. After that point the create call uses an empty dictionary.
The filtering code depends on the create dictionary being empty.
It may not operate properly on very old roundup databases.
Changed calls to logging.getLogger to roundup.hyperdb.backends to
allow filtering the back end while keeping hyperdb logging.
In cgi/templating.py, changed history() function consolidating
handiling of link and unlink actions
Added tests for quiet property filtering and permission filtering
of history.
| author | John Rouillard <rouilj@ieee.org> |
|---|---|
| date | Fri, 14 Apr 2017 23:24:18 -0400 |
| parents | e1e40674a0bc |
| children | 198b6e810c67 |
comparison
equal
deleted
inserted
replaced
| 5231:8743b7226dc7 | 5232:462b0f76fce8 |
|---|---|
| 21 __docformat__ = 'restructuredtext' | 21 __docformat__ = 'restructuredtext' |
| 22 | 22 |
| 23 # standard python modules | 23 # standard python modules |
| 24 import os, re, shutil, sys, weakref | 24 import os, re, shutil, sys, weakref |
| 25 import traceback | 25 import traceback |
| 26 import logging | |
| 26 | 27 |
| 27 # roundup modules | 28 # roundup modules |
| 28 import date, password | 29 import date, password |
| 29 from support import ensureParentsExist, PrioList | 30 from support import ensureParentsExist, PrioList |
| 30 from roundup.i18n import _ | 31 from roundup.i18n import _ |
| 31 from roundup.cgi.exceptions import DetectorError | 32 from roundup.cgi.exceptions import DetectorError |
| 33 | |
| 34 logger = logging.getLogger('roundup.hyperdb') | |
| 32 | 35 |
| 33 # | 36 # |
| 34 # Types | 37 # Types |
| 35 # | 38 # |
| 36 class _Type(object): | 39 class _Type(object): |
| 779 | 782 |
| 780 def addjournal(self, classname, nodeid, action, params): | 783 def addjournal(self, classname, nodeid, action, params): |
| 781 """ Journal the Action | 784 """ Journal the Action |
| 782 'action' may be: | 785 'action' may be: |
| 783 | 786 |
| 784 'create' or 'set' -- 'params' is a dictionary of property values | 787 'set' -- 'params' is a dictionary of property values |
| 788 'create' -- 'params' is an empty dictionary as of | |
| 789 Wed Nov 06 11:38:43 2002 +0000 | |
| 785 'link' or 'unlink' -- 'params' is (classname, nodeid, propname) | 790 'link' or 'unlink' -- 'params' is (classname, nodeid, propname) |
| 786 'retire' -- 'params' is None | 791 'retired' or 'restored'-- 'params' is None |
| 787 """ | 792 """ |
| 788 raise NotImplementedError | 793 raise NotImplementedError |
| 789 | 794 |
| 790 def getjournal(self, classname, nodeid): | 795 def getjournal(self, classname, nodeid): |
| 791 """ get the journal for id | 796 """ get the journal for id |
| 989 The node is completely removed from the hyperdb, including all journal | 994 The node is completely removed from the hyperdb, including all journal |
| 990 entries. It will no longer be available, and will generally break code | 995 entries. It will no longer be available, and will generally break code |
| 991 if there are any references to the node. | 996 if there are any references to the node. |
| 992 """ | 997 """ |
| 993 | 998 |
| 994 def history(self, nodeid): | 999 def history(self, nodeid, enforceperm=True, skipquiet=True): |
| 995 """Retrieve the journal of edits on a particular node. | 1000 """Retrieve the journal of edits on a particular node. |
| 996 | 1001 |
| 997 'nodeid' must be the id of an existing node of this class or an | 1002 'nodeid' must be the id of an existing node of this class or an |
| 998 IndexError is raised. | 1003 IndexError is raised. |
| 999 | 1004 |
| 1001 | 1006 |
| 1002 (date, tag, action, params) | 1007 (date, tag, action, params) |
| 1003 | 1008 |
| 1004 'date' is a Timestamp object specifying the time of the change and | 1009 'date' is a Timestamp object specifying the time of the change and |
| 1005 'tag' is the journaltag specified when the database was opened. | 1010 'tag' is the journaltag specified when the database was opened. |
| 1011 | |
| 1012 If the property to be displayed is a quiet property, it will | |
| 1013 not be shown. This can be disabled by setting skipquiet=False. | |
| 1014 | |
| 1015 If the user requesting the history does not have View access | |
| 1016 to the property, the journal entry will not be shown. This can | |
| 1017 be disabled by setting enforceperm=False. | |
| 1006 """ | 1018 """ |
| 1007 if not self.do_journal: | 1019 if not self.do_journal: |
| 1008 raise ValueError('Journalling is disabled for this class') | 1020 raise ValueError('Journalling is disabled for this class') |
| 1009 return self.db.getjournal(self.classname, nodeid) | 1021 |
| 1022 perm = self.db.security.hasPermission | |
| 1023 journal = [] | |
| 1024 | |
| 1025 debug_logging = logger.isEnabledFor(logging.DEBUG) | |
| 1026 | |
| 1027 for j in self.db.getjournal(self.classname, nodeid): | |
| 1028 id, evt_date, user, action, args = j | |
| 1029 if debug_logging: | |
| 1030 j_repr = "%s"%(j,) | |
| 1031 else: | |
| 1032 j_repr='' | |
| 1033 if args and type(args) == type({}): | |
| 1034 for k in args.keys(): | |
| 1035 if skipquiet and self.properties[k].quiet: | |
| 1036 logger.debug("skipping quiet property %s in %s", | |
| 1037 k, j_repr) | |
| 1038 del j[4][k] | |
| 1039 continue | |
| 1040 if enforceperm and not perm("View", | |
| 1041 self.db.getuid(), | |
| 1042 self.classname, | |
| 1043 property=k ): | |
| 1044 logger.debug("skipping unViewable property %s in %s", | |
| 1045 k, j_repr) | |
| 1046 del j[4][k] | |
| 1047 continue | |
| 1048 if not args: | |
| 1049 logger.debug("Omitting journal entry for %s%s" | |
| 1050 " all props quiet in: %s", | |
| 1051 self.classname, nodeid, j_repr) | |
| 1052 continue | |
| 1053 journal.append(j) | |
| 1054 elif action in ['link', 'unlink' ] and type(args) == type(()): | |
| 1055 if len(args) == 3: | |
| 1056 linkcl, linkid, key = args | |
| 1057 cls = self.db.getclass(linkcl) | |
| 1058 if skipquiet and cls.properties[key].quiet: | |
| 1059 logger.debug("skipping quiet property %s in %s", | |
| 1060 key, j_repr) | |
| 1061 continue | |
| 1062 if enforceperm and not perm("View", | |
| 1063 self.db.getuid(), | |
| 1064 self.classname, | |
| 1065 property=key): | |
| 1066 logger.debug("skipping unViewable property %s in", | |
| 1067 key, j_repr) | |
| 1068 continue | |
| 1069 journal.append(j) | |
| 1070 else: | |
| 1071 logger.error("Invalid %s journal entry for %s%s: %s", | |
| 1072 action, self.classname, nodeid, j) | |
| 1073 elif action in ['create', 'retired', 'restored']: | |
| 1074 journal.append(j) | |
| 1075 else: | |
| 1076 logger.warning("Possibly malformed journal for %s%s %s", | |
| 1077 self.classname, nodeid, j) | |
| 1078 return journal | |
| 1010 | 1079 |
| 1011 # Locating nodes: | 1080 # Locating nodes: |
| 1012 def hasnode(self, nodeid): | 1081 def hasnode(self, nodeid): |
| 1013 """Determine if the given nodeid actually exists | 1082 """Determine if the given nodeid actually exists |
| 1014 """ | 1083 """ |
| 1581 return self.cl.set(self.nodeid, **{name: value}) | 1650 return self.cl.set(self.nodeid, **{name: value}) |
| 1582 except KeyError, value: | 1651 except KeyError, value: |
| 1583 raise AttributeError, str(value) | 1652 raise AttributeError, str(value) |
| 1584 def __setitem__(self, name, value): | 1653 def __setitem__(self, name, value): |
| 1585 self.cl.set(self.nodeid, **{name: value}) | 1654 self.cl.set(self.nodeid, **{name: value}) |
| 1586 def history(self): | 1655 def history(self, enforceperm=True, skipquiet=True): |
| 1587 return self.cl.history(self.nodeid) | 1656 return self.cl.history(self.nodeid, |
| 1657 enforceperm=enforceperm, | |
| 1658 skipquiet=skipquiet ) | |
| 1588 def retire(self): | 1659 def retire(self): |
| 1589 return self.cl.retire(self.nodeid) | 1660 return self.cl.retire(self.nodeid) |
| 1590 | 1661 |
| 1591 | 1662 |
| 1592 def Choice(name, db, *options): | 1663 def Choice(name, db, *options): |
