Code

Remove the '=' padding from cookie value so quoting isn't an issue.
[roundup.git] / roundup / cgi_client.py
index b17745ce97395569970376bb2ee9d5545f0fe8f2..f1e960e91ed8902a9bf1e8434438aef7e9df7227 100644 (file)
@@ -15,7 +15,7 @@
 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 # 
-# $Id: cgi_client.py,v 1.48 2001-11-03 01:30:18 richard Exp $
+# $Id: cgi_client.py,v 1.54 2001-11-07 01:16:12 richard Exp $
 
 import os, cgi, pprint, StringIO, urlparse, re, traceback, mimetypes
 import binascii, Cookie, time
@@ -61,7 +61,11 @@ class Client:
 
         self.form = cgi.FieldStorage(environ=env)
         self.headers_done = 0
-        self.debug = 0
+        try:
+            self.debug = int(env.get("ROUNDUP_DEBUG", 0))
+        except ValueError:
+            # someone gave us a non-int debug level, turn it off
+            self.debug = 0
 
     def getuid(self):
         return self.db.user.lookup(self.user)
@@ -76,6 +80,8 @@ class Client:
             self.request.send_header(*entry)
         self.request.end_headers()
         self.headers_done = 1
+        if self.debug:
+            self.headers_sent = headers
 
     def pagehead(self, title, message=None):
         url = self.env['SCRIPT_NAME'] + '/'
@@ -140,14 +146,23 @@ class Client:
             if keys:
                 self.write('<dt><b>Form entries</b></dt>')
                 for k in self.form.keys():
-                    v = str(self.form[k].value)
-                    self.write('<dd><em>%s</em>:%s</dd>'%(k, cgi.escape(v)))
+                    v = self.form.getvalue(k, "<empty>")
+                    if type(v) is type([]):
+                        # Multiple username fields specified
+                        v = "|".join(v)
+                    self.write('<dd><em>%s</em>=%s</dd>'%(k, cgi.escape(v)))
+            keys = self.headers_sent.keys()
+            keys.sort()
+            self.write('<dt><b>Sent these HTTP headers</b></dt>')
+            for k in keys:
+                v = self.headers_sent[k]
+                self.write('<dd><em>%s</em>=%s</dd>'%(k, cgi.escape(v)))
             keys = self.env.keys()
             keys.sort()
             self.write('<dt><b>CGI environment</b></dt>')
             for k in keys:
                 v = self.env[k]
-                self.write('<dd><em>%s</em>:%s</dd>'%(k, cgi.escape(v)))
+                self.write('<dd><em>%s</em>=%s</dd>'%(k, cgi.escape(v)))
             self.write('</dl></small>')
         self.write('</body></html>')
 
@@ -312,13 +327,58 @@ class Client:
     showmsg = shownode
 
     def showuser(self, message=None):
-        ''' display an item
+        '''Display a user page for editing. Make sure the user is allowed
+            to edit this node, and also check for password changes.
         '''
-        if self.user in ('admin', self.db.user.get(self.nodeid, 'username')):
-            self.shownode(message)
-        else:
+        if self.user == 'anonymous':
             raise Unauthorised
 
+        user = self.db.user
+
+        # get the username of the node being edited
+        node_user = user.get(self.nodeid, 'username')
+
+        if self.user not in ('admin', node_user):
+            raise Unauthorised
+
+        #
+        # perform any editing
+        #
+        keys = self.form.keys()
+        num_re = re.compile('^\d+$')
+        if keys:
+            try:
+                props, changed = parsePropsFromForm(self.db, user, self.form,
+                    self.nodeid)
+                if self.nodeid == self.getuid() and 'password' in changed:
+                    set_cookie = self.form['password'].value.strip()
+                else:
+                    set_cookie = 0
+                user.set(self.nodeid, **props)
+                self._post_editnode(self.nodeid, changed)
+                # and some feedback for the user
+                message = '%s edited ok'%', '.join(changed)
+            except:
+                s = StringIO.StringIO()
+                traceback.print_exc(None, s)
+                message = '<pre>%s</pre>'%cgi.escape(s.getvalue())
+        else:
+            set_cookie = 0
+
+        # fix the cookie if the password has changed
+        if set_cookie:
+            self.set_cookie(self.user, set_cookie)
+
+        #
+        # now the display
+        #
+        self.pagehead('User: %s'%node_user, message)
+
+        # use the template to display the item
+        item = htmltemplate.ItemTemplate(self, self.TEMPLATES, 'user')
+        item.render(self.nodeid)
+        self.pagefoot()
+
     def showfile(self):
         ''' display a file
         '''
@@ -526,7 +586,7 @@ class Client:
         else:
             raise Unauthorised
 
-    def login(self, message=None):
+    def login(self, message=None, newuser_form=None):
         self.pagehead('Login to roundup', message)
         self.write('''
 <table>
@@ -544,30 +604,35 @@ class Client:
             self.write('</table>')
             self.pagefoot()
             return
+        values = {'realname': '', 'organisation': '', 'address': '',
+            'phone': '', 'username': '', 'password': '', 'confirm': ''}
+        if newuser_form is not None:
+            for key in newuser_form.keys():
+                values[key] = newuser_form[key].value
         self.write('''
 <p>
 <tr><td colspan=2 class="strong-header">New User Registration</td></tr>
 <tr><td colspan=2><em>marked items</em> are optional...</td></tr>
 <form action="newuser_action" method=POST>
 <tr><td align=right><em>Name: </em></td>
-    <td><input name="realname"></td></tr>
+    <td><input name="realname" value="%(realname)s"></td></tr>
 <tr><td align=right><em>Organisation: </em></td>
-    <td><input name="organisation"></td></tr>
+    <td><input name="organisation" value="%(organisation)s"></td></tr>
 <tr><td align=right>E-Mail Address: </td>
-    <td><input name="address"></td></tr>
+    <td><input name="address" value="%(address)s"></td></tr>
 <tr><td align=right><em>Phone: </em></td>
-    <td><input name="phone"></td></tr>
+    <td><input name="phone" value="%(phone)s"></td></tr>
 <tr><td align=right>Preferred Login name: </td>
-    <td><input name="username"></td></tr>
+    <td><input name="username" value="%(username)s"></td></tr>
 <tr><td align=right>Password: </td>
-    <td><input type="password" name="password"></td></tr>
+    <td><input type="password" name="password" value="%(password)s"></td></tr>
 <tr><td align=right>Password Again: </td>
-    <td><input type="password" name="confirm"></td></tr>
+    <td><input type="password" name="confirm" value="%(confirm)s"></td></tr>
 <tr><td></td>
     <td><input type="submit" value="Register"></td></tr>
 </form>
 </table>
-''')
+'''%values)
         self.pagefoot()
 
     def login_action(self, message=None):
@@ -578,7 +643,6 @@ class Client:
             password = self.form['__login_password'].value
         else:
             password = ''
-        print self.user, password
         # make sure the user exists
         try:
             uid = self.db.user.lookup(self.user)
@@ -593,14 +657,22 @@ class Client:
             self.make_user_anonymous()
             return self.login(message='Incorrect password')
 
-        # construct the cookie
-        uid = self.db.user.lookup(self.user)
-        user = binascii.b2a_base64('%s:%s'%(self.user, password)).strip()
-        path = '/'.join((self.env['SCRIPT_NAME'], self.env['INSTANCE_NAME'],
-            ''))
-        self.header({'Set-Cookie': 'roundup_user=%s; Path=%s;'%(user, path)})
+        self.set_cookie(self.user, password)
         return self.index()
 
+    def set_cookie(self, user, password):
+        # construct the cookie
+        user = binascii.b2a_base64('%s:%s'%(user, password)).strip()
+        if user[-1] == '=':
+          if user[-2] == '=':
+            user = user[:-2]
+          else:
+            user = user[:-1]
+        expire = Cookie._getdate(86400*365)
+        path = '/'.join((self.env['SCRIPT_NAME'], self.env['INSTANCE_NAME']))
+        self.header({'Set-Cookie': 'roundup_user=%s; expires=%s; Path=%s;' % (
+            user, expire, path)})
+
     def make_user_anonymous(self):
         # make us anonymous if we can
         try:
@@ -612,11 +684,11 @@ class Client:
     def logout(self, message=None):
         self.make_user_anonymous()
         # construct the logout cookie
-        path = '/'.join((self.env['SCRIPT_NAME'], self.env['INSTANCE_NAME'],
-            ''))
         now = Cookie._getdate()
+        path = '/'.join((self.env['SCRIPT_NAME'], self.env['INSTANCE_NAME']))
         self.header({'Set-Cookie':
-            'roundup_user=deleted; Max-Age=0; expires=%s; Path=%s;'%(now, path)})
+            'roundup_user=deleted; Max-Age=0; expires=%s; Path=%s;'%(now,
+            path)})
         return self.login()
 
     def newuser_action(self, message=None):
@@ -628,17 +700,15 @@ class Client:
         self.db = self.instance.open('admin')
 
         # TODO: pre-check the required fields and username key property
-        cl = self.db.classes['user']
-        props, dummy = parsePropsFromForm(self.db, cl, self.form)
-        uid = cl.create(**props)
-        self.user = self.db.user.get(uid, 'username')
-        password = self.db.user.get(uid, 'password')
-        # construct the cookie
-        uid = self.db.user.lookup(self.user)
-        user = binascii.b2a_base64('%s:%s'%(self.user, password)).strip()
-        path = '/'.join((self.env['SCRIPT_NAME'], self.env['INSTANCE_NAME'],
-            ''))
-        self.header({'Set-Cookie': 'roundup_user=%s; Path=%s;'%(user, path)})
+        cl = self.db.user
+        try:
+            props, dummy = parsePropsFromForm(self.db, cl, self.form)
+            uid = cl.create(**props)
+        except ValueError, message:
+            return self.login(message, newuser_form=self.form)
+        self.user = cl.get(uid, 'username')
+        password = cl.get(uid, 'password')
+        self.set_cookie(self.user, self.form['password'].value)
         return self.index()
 
     def main(self, dre=re.compile(r'([^\d]+)(\d+)'),
@@ -651,6 +721,8 @@ class Client:
         if (cookie.has_key('roundup_user') and
                 cookie['roundup_user'].value != 'deleted'):
             cookie = cookie['roundup_user'].value
+            if len(cookie)%4:
+              cookie = cookie + '='*(4-len(cookie)%4)
             user, password = binascii.a2b_base64(cookie).split(':')
             # make sure the user exists
             try:
@@ -878,6 +950,34 @@ def parsePropsFromForm(db, cl, form, nodeid=0):
 
 #
 # $Log: not supported by cvs2svn $
+# Revision 1.53  2001/11/06 23:22:05  jhermann
+# More IE fixes: it does not like quotes around cookie values; in the
+# hope this does not break anything for other browser; if it does, we
+# need to check HTTP_USER_AGENT
+#
+# Revision 1.52  2001/11/06 23:11:22  jhermann
+# Fixed debug output in page footer; added expiry date to the login cookie
+# (expires 1 year in the future) to prevent probs with certain versions
+# of IE
+#
+# Revision 1.51  2001/11/06 22:00:34  jhermann
+# Get debug level from ROUNDUP_DEBUG env var
+#
+# Revision 1.50  2001/11/05 23:45:40  richard
+# Fixed newuser_action so it sets the cookie with the unencrypted password.
+# Also made it present nicer error messages (not tracebacks).
+#
+# Revision 1.49  2001/11/04 03:07:12  richard
+# Fixed various cookie-related bugs:
+#  . bug #477685 ] base64.decodestring breaks
+#  . bug #477837 ] lynx does not like the cookie
+#  . bug #477892 ] Password edit doesn't fix login cookie
+# Also closed a security hole - a logged-in user could edit another user's
+# details.
+#
+# Revision 1.48  2001/11/03 01:30:18  richard
+# Oops. uses pagefoot now.
+#
 # Revision 1.47  2001/11/03 01:29:28  richard
 # Login page didn't have all close tags.
 #