annotate roundup/cgi/TAL/TALParser.py @ 8411:ef1ea918b07a reauth-confirm_id

feat(security): Add user confirmation/reauth for sensitive changes Auditors can raise Reauth(reason) exception to require the user to enter a token (e.g. account password) to verify the user is performing the change. Naming is subject to change. actions.py: New ReauthAction class handler and verifyPassword() method for overriding if needed. client.py: Handle Reauth exception by calling Client:reauth() method. Default client:reauth method. Add 'reauth' action declaration. exceptions.py: Define and document Reauth exception as a subclass of RoundupCGIException. templating.py: Define method utils.embed_form_fields(). The original form making a change to the database has a lot of form fields. These need to be resubmitted to Roundup as part of the form submission that verifies the user's password. This method turns all non file form fields into type=hidden inputs. It escapes the names and values to prevent XSS. For file form fields, it base64 encodes the contents and puts them in hidden pre blocks. The pre blocks have data attributes for the filename, filetype and the original field name. (Note the original field name is not used.) This stops the file content data (maybe binary e.g. jpegs) from breaking the html page. The reauth template runs JavaScript that turns the encoded data inside the pre tags back into a file. Then it adds a multiple file input control to the page and attaches all the files to it. This file input is submitted with the rest of the fields. _generic.reauth.html (multiple tracker templates): Generates a form with id=reauth_form to: display any message from the Reauth exception to the user (e.g. why user is asked to auth). get the user's password submit the form embed all the form data that triggered the reauth recreate any file data that was submitted as part of the form and generate a new file input to push the data to the back end It has the JavaScript routine (as an IIFE) that regenerates a file input without user intervention. All the TAL based tracker templates use the same form. There is also one for the jinja2 template. The JavaScript for both is the same. reference.txt: document embed_form_fields utility method. upgrading.txt: initial upgrading docs. TODO: Finalize naming. I am leaning toward ConfirmID rather than Reauth. Still looking for a standard name for this workflow. Externalize the javascript in _generic.reauth.html to a seperate file and use utils.readfile() to embed it or change the script to load it from a @@file url. Clean up upgrading.txt with just steps to implement and less feature detail/internals. Document internals/troubleshooting in reference.txt. Add tests using live server.
author John Rouillard <rouilj@ieee.org>
date Mon, 11 Aug 2025 14:01:12 -0400
parents 23b8e6067f7c
children
Ignore whitespace changes - Everywhere: Within whitespace: At end of lines:
rev   line source
1049
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
1 ##############################################################################
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
2 #
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
3 # Copyright (c) 2001, 2002 Zope Corporation and Contributors.
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
4 # All Rights Reserved.
2348
8c2402a78bb0 beginning getting ZPT up to date: TAL first
Richard Jones <richard@users.sourceforge.net>
parents: 2005
diff changeset
5 #
1049
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
6 # This software is subject to the provisions of the Zope Public License,
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
7 # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
8 # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
9 # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
10 # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
2348
8c2402a78bb0 beginning getting ZPT up to date: TAL first
Richard Jones <richard@users.sourceforge.net>
parents: 2005
diff changeset
11 # FOR A PARTICULAR PURPOSE.
8c2402a78bb0 beginning getting ZPT up to date: TAL first
Richard Jones <richard@users.sourceforge.net>
parents: 2005
diff changeset
12 #
1049
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
13 ##############################################################################
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
14 """
2348
8c2402a78bb0 beginning getting ZPT up to date: TAL first
Richard Jones <richard@users.sourceforge.net>
parents: 2005
diff changeset
15 Parse XML and compile to TALInterpreter intermediate code.
8c2402a78bb0 beginning getting ZPT up to date: TAL first
Richard Jones <richard@users.sourceforge.net>
parents: 2005
diff changeset
16 """
1049
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
17
5388
d26921b851c3 Python 3 preparation: make relative imports explicit.
Joseph Myers <jsm@polyomino.org.uk>
parents: 5377
diff changeset
18 from .XMLParser import XMLParser
d26921b851c3 Python 3 preparation: make relative imports explicit.
Joseph Myers <jsm@polyomino.org.uk>
parents: 5377
diff changeset
19 from .TALDefs import XML_NS, ZOPE_I18N_NS, ZOPE_METAL_NS, ZOPE_TAL_NS
d26921b851c3 Python 3 preparation: make relative imports explicit.
Joseph Myers <jsm@polyomino.org.uk>
parents: 5377
diff changeset
20 from .TALGenerator import TALGenerator
1049
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
21
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
22 class TALParser(XMLParser):
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
23
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
24 ordered_attributes = 1
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
25
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
26 def __init__(self, gen=None): # Override
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
27 XMLParser.__init__(self)
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
28 if gen is None:
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
29 gen = TALGenerator()
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
30 self.gen = gen
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
31 self.nsStack = []
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
32 self.nsDict = {XML_NS: 'xml'}
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
33 self.nsNew = []
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
34
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
35 def getCode(self):
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
36 return self.gen.getCode()
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
37
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
38 def getWarnings(self):
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
39 return ()
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
40
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
41 def StartNamespaceDeclHandler(self, prefix, uri):
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
42 self.nsStack.append(self.nsDict.copy())
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
43 self.nsDict[uri] = prefix
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
44 self.nsNew.append((prefix, uri))
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
45
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
46 def EndNamespaceDeclHandler(self, prefix):
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
47 self.nsDict = self.nsStack.pop()
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
48
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
49 def StartElementHandler(self, name, attrs):
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
50 if self.ordered_attributes:
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
51 # attrs is a list of alternating names and values
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
52 attrlist = []
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
53 for i in range(0, len(attrs), 2):
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
54 key = attrs[i]
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
55 value = attrs[i+1]
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
56 attrlist.append((key, value))
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
57 else:
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
58 # attrs is a dict of {name: value}
5395
23b8e6067f7c Python 3 preparation: update calls to dict methods.
Joseph Myers <jsm@polyomino.org.uk>
parents: 5388
diff changeset
59 attrlist = sorted(attrs.items()) # Sorted for definiteness
2348
8c2402a78bb0 beginning getting ZPT up to date: TAL first
Richard Jones <richard@users.sourceforge.net>
parents: 2005
diff changeset
60 name, attrlist, taldict, metaldict, i18ndict \
8c2402a78bb0 beginning getting ZPT up to date: TAL first
Richard Jones <richard@users.sourceforge.net>
parents: 2005
diff changeset
61 = self.process_ns(name, attrlist)
1049
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
62 attrlist = self.xmlnsattrs() + attrlist
2348
8c2402a78bb0 beginning getting ZPT up to date: TAL first
Richard Jones <richard@users.sourceforge.net>
parents: 2005
diff changeset
63 self.gen.emitStartElement(name, attrlist, taldict, metaldict, i18ndict)
1049
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
64
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
65 def process_ns(self, name, attrlist):
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
66 taldict = {}
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
67 metaldict = {}
2348
8c2402a78bb0 beginning getting ZPT up to date: TAL first
Richard Jones <richard@users.sourceforge.net>
parents: 2005
diff changeset
68 i18ndict = {}
1049
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
69 fixedattrlist = []
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
70 name, namebase, namens = self.fixname(name)
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
71 for key, value in attrlist:
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
72 key, keybase, keyns = self.fixname(key)
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
73 ns = keyns or namens # default to tag namespace
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
74 item = key, value
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
75 if ns == 'metal':
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
76 metaldict[keybase] = value
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
77 item = item + ("metal",)
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
78 elif ns == 'tal':
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
79 taldict[keybase] = value
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
80 item = item + ("tal",)
2348
8c2402a78bb0 beginning getting ZPT up to date: TAL first
Richard Jones <richard@users.sourceforge.net>
parents: 2005
diff changeset
81 elif ns == 'i18n':
5377
12fe83f90f0d Python 3 preparation: use repr() instead of ``.
Joseph Myers <jsm@polyomino.org.uk>
parents: 2348
diff changeset
82 assert 0, "dealing with i18n: " + repr((keybase, value))
2348
8c2402a78bb0 beginning getting ZPT up to date: TAL first
Richard Jones <richard@users.sourceforge.net>
parents: 2005
diff changeset
83 i18ndict[keybase] = value
8c2402a78bb0 beginning getting ZPT up to date: TAL first
Richard Jones <richard@users.sourceforge.net>
parents: 2005
diff changeset
84 item = item + ('i18n',)
1049
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
85 fixedattrlist.append(item)
2348
8c2402a78bb0 beginning getting ZPT up to date: TAL first
Richard Jones <richard@users.sourceforge.net>
parents: 2005
diff changeset
86 if namens in ('metal', 'tal', 'i18n'):
1049
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
87 taldict['tal tag'] = namens
2348
8c2402a78bb0 beginning getting ZPT up to date: TAL first
Richard Jones <richard@users.sourceforge.net>
parents: 2005
diff changeset
88 return name, fixedattrlist, taldict, metaldict, i18ndict
1049
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
89
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
90 def xmlnsattrs(self):
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
91 newlist = []
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
92 for prefix, uri in self.nsNew:
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
93 if prefix:
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
94 key = "xmlns:" + prefix
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
95 else:
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
96 key = "xmlns"
2348
8c2402a78bb0 beginning getting ZPT up to date: TAL first
Richard Jones <richard@users.sourceforge.net>
parents: 2005
diff changeset
97 if uri in (ZOPE_METAL_NS, ZOPE_TAL_NS, ZOPE_I18N_NS):
1049
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
98 item = (key, uri, "xmlns")
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
99 else:
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
100 item = (key, uri)
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
101 newlist.append(item)
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
102 self.nsNew = []
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
103 return newlist
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
104
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
105 def fixname(self, name):
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
106 if ' ' in name:
2348
8c2402a78bb0 beginning getting ZPT up to date: TAL first
Richard Jones <richard@users.sourceforge.net>
parents: 2005
diff changeset
107 uri, name = name.split(' ')
1049
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
108 prefix = self.nsDict[uri]
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
109 prefixed = name
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
110 if prefix:
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
111 prefixed = "%s:%s" % (prefix, name)
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
112 ns = 'x'
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
113 if uri == ZOPE_TAL_NS:
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
114 ns = 'tal'
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
115 elif uri == ZOPE_METAL_NS:
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
116 ns = 'metal'
2348
8c2402a78bb0 beginning getting ZPT up to date: TAL first
Richard Jones <richard@users.sourceforge.net>
parents: 2005
diff changeset
117 elif uri == ZOPE_I18N_NS:
8c2402a78bb0 beginning getting ZPT up to date: TAL first
Richard Jones <richard@users.sourceforge.net>
parents: 2005
diff changeset
118 ns = 'i18n'
1049
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
119 return (prefixed, name, ns)
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
120 return (name, name, None)
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
121
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
122 def EndElementHandler(self, name):
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
123 name = self.fixname(name)[0]
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
124 self.gen.emitEndElement(name)
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
125
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
126 def DefaultHandler(self, text):
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
127 self.gen.emitRawText(text)
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
128
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
129 def test():
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
130 import sys
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
131 p = TALParser()
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
132 file = "tests/input/test01.xml"
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
133 if sys.argv[1:]:
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
134 file = sys.argv[1]
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
135 p.parseFile(file)
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
136 program, macros = p.getCode()
5388
d26921b851c3 Python 3 preparation: make relative imports explicit.
Joseph Myers <jsm@polyomino.org.uk>
parents: 5377
diff changeset
137 from .TALInterpreter import TALInterpreter
d26921b851c3 Python 3 preparation: make relative imports explicit.
Joseph Myers <jsm@polyomino.org.uk>
parents: 5377
diff changeset
138 from .DummyEngine import DummyEngine
1049
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
139 engine = DummyEngine(macros)
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
140 TALInterpreter(program, macros, engine, sys.stdout, wrap=0)()
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
141
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
142 if __name__ == "__main__":
Richard Jones <richard@users.sourceforge.net>
parents:
diff changeset
143 test()

Roundup Issue Tracker: http://roundup-tracker.org/