Code

query saving fix
[roundup.git] / roundup / cgi / actions.py
1 import re, cgi, StringIO, urllib, Cookie, time, random
3 from roundup import hyperdb, token, date, password, rcsv
4 from roundup.i18n import _
5 from roundup.cgi import templating
6 from roundup.cgi.exceptions import Redirect, Unauthorised, SeriousError
7 from roundup.mailgw import uidFromAddress
9 __all__ = ['Action', 'ShowAction', 'RetireAction', 'SearchAction',
10            'EditCSVAction', 'EditItemAction', 'PassResetAction',
11            'ConfRegoAction', 'RegisterAction', 'LoginAction', 'LogoutAction',
12            'NewItemAction']
14 # used by a couple of routines
15 chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
17 class Action:
18     def __init__(self, client):
19         self.client = client
20         self.form = client.form
21         self.db = client.db
22         self.nodeid = client.nodeid
23         self.template = client.template
24         self.classname = client.classname
25         self.userid = client.userid
26         self.base = client.base
27         self.user = client.user
29     def execute(self):
30         """Execute the action specified by this object."""
31         self.permission()
32         self.handle()
34     name = ''
35     permissionType = None
36     def permission(self):
37         """Check whether the user has permission to execute this action.
39         True by default. If the permissionType attribute is a string containing
40         a simple permission, check whether the user has that permission.
41         Subclasses must also define the name attribute if they define
42         permissionType.
44         Despite having this permission, users may still be unauthorised to
45         perform parts of actions. It is up to the subclasses to detect this.
46         """
47         if (self.permissionType and
48                 not self.hasPermission(self.permissionType)):
49             info = {'action': self.name, 'classname': self.classname}
50             raise Unauthorised, _('You do not have permission to '
51                 '%(action)s the %(classname)s class.')%info
53     def hasPermission(self, permission):
54         """Check whether the user has 'permission' on the current class."""
55         return self.db.security.hasPermission(permission, self.client.userid,
56             self.client.classname)
58 class ShowAction(Action):
59     def handle(self, typere=re.compile('[@:]type'),
60                numre=re.compile('[@:]number')):
61         """Show a node of a particular class/id."""
62         t = n = ''
63         for key in self.form.keys():
64             if typere.match(key):
65                 t = self.form[key].value.strip()
66             elif numre.match(key):
67                 n = self.form[key].value.strip()
68         if not t:
69             raise ValueError, 'No type specified'
70         if not n:
71             raise SeriousError, _('No ID entered')
72         try:
73             int(n)
74         except ValueError:
75             d = {'input': n, 'classname': t}
76             raise SeriousError, _(
77                 '"%(input)s" is not an ID (%(classname)s ID required)')%d
78         url = '%s%s%s'%(self.db.config.TRACKER_WEB, t, n)
79         raise Redirect, url
81 class RetireAction(Action):
82     name = 'retire'
83     permissionType = 'Edit'
85     def handle(self):
86         """Retire the context item."""
87         # if we want to view the index template now, then unset the nodeid
88         # context info (a special-case for retire actions on the index page)
89         nodeid = self.nodeid
90         if self.template == 'index':
91             self.client.nodeid = None
93         # make sure we don't try to retire admin or anonymous
94         if self.classname == 'user' and \
95                 self.db.user.get(nodeid, 'username') in ('admin', 'anonymous'):
96             raise ValueError, _('You may not retire the admin or anonymous user')
98         # do the retire
99         self.db.getclass(self.classname).retire(nodeid)
100         self.db.commit()
102         self.client.ok_message.append(
103             _('%(classname)s %(itemid)s has been retired')%{
104                 'classname': self.classname.capitalize(), 'itemid': nodeid})
106 class SearchAction(Action):
107     name = 'search'
108     permissionType = 'View'
110     def handle(self, wcre=re.compile(r'[\s,]+')):
111         """Mangle some of the form variables.
113         Set the form ":filter" variable based on the values of the filter
114         variables - if they're set to anything other than "dontcare" then add
115         them to :filter.
117         Handle the ":queryname" variable and save off the query to the user's
118         query list.
120         Split any String query values on whitespace and comma.
122         """
123         self.fakeFilterVars()
124         queryname = self.getQueryName()
126         # handle saving the query params
127         if queryname:
128             # parse the environment and figure what the query _is_
129             req = templating.HTMLRequest(self.client)
131             # The [1:] strips off the '?' character, it isn't part of the
132             # query string.
133             url = req.indexargs_href('', {})[1:]
135             # handle editing an existing query
136             try:
137                 qid = self.db.query.lookup(queryname)
138                 self.db.query.set(qid, klass=self.classname, url=url)
139             except KeyError:
140                 # create a query
141                 qid = self.db.query.create(name=queryname,
142                     klass=self.classname, url=url)
144             # and add it to the user's query multilink
145             queries = self.db.user.get(self.userid, 'queries')
146             if qid not in queries:
147                 queries.append(qid)
148                 self.db.user.set(self.userid, queries=queries)
150             # commit the query change to the database
151             self.db.commit()
153     def fakeFilterVars(self):
154         """Add a faked :filter form variable for each filtering prop."""
155         props = self.db.classes[self.classname].getprops()
156         for key in self.form.keys():
157             if not props.has_key(key):
158                 continue
159             if isinstance(self.form[key], type([])):
160                 # search for at least one entry which is not empty
161                 for minifield in self.form[key]:
162                     if minifield.value:
163                         break
164                 else:
165                     continue
166             else:
167                 if not self.form[key].value:
168                     continue
169                 if isinstance(props[key], hyperdb.String):
170                     v = self.form[key].value
171                     l = token.token_split(v)
172                     if len(l) > 1 or l[0] != v:
173                         self.form.value.remove(self.form[key])
174                         # replace the single value with the split list
175                         for v in l:
176                             self.form.value.append(cgi.MiniFieldStorage(key, v))
178             self.form.value.append(cgi.MiniFieldStorage('@filter', key))
180     FV_QUERYNAME = re.compile(r'[@:]queryname')
181     def getQueryName(self):
182         for key in self.form.keys():
183             if self.FV_QUERYNAME.match(key):
184                 return self.form[key].value.strip()
185         return ''
187 class EditCSVAction(Action):
188     name = 'edit'
189     permissionType = 'Edit'
191     def handle(self):
192         """Performs an edit of all of a class' items in one go.
194         The "rows" CGI var defines the CSV-formatted entries for the class. New
195         nodes are identified by the ID 'X' (or any other non-existent ID) and
196         removed lines are retired.
198         """
199         # get the CSV module
200         if rcsv.error:
201             self.client.error_message.append(_(rcsv.error))
202             return
204         cl = self.db.classes[self.classname]
205         idlessprops = cl.getprops(protected=0).keys()
206         idlessprops.sort()
207         props = ['id'] + idlessprops
209         # do the edit
210         rows = StringIO.StringIO(self.form['rows'].value)
211         reader = rcsv.reader(rows, rcsv.comma_separated)
212         found = {}
213         line = 0
214         for values in reader:
215             line += 1
216             if line == 1: continue
217             # skip property names header
218             if values == props:
219                 continue
221             # extract the nodeid
222             nodeid, values = values[0], values[1:]
223             found[nodeid] = 1
225             # see if the node exists
226             if nodeid in ('x', 'X') or not cl.hasnode(nodeid):
227                 exists = 0
228             else:
229                 exists = 1
231             # confirm correct weight
232             if len(idlessprops) != len(values):
233                 self.client.error_message.append(
234                     _('Not enough values on line %(line)s')%{'line':line})
235                 return
237             # extract the new values
238             d = {}
239             for name, value in zip(idlessprops, values):
240                 prop = cl.properties[name]
241                 value = value.strip()
242                 # only add the property if it has a value
243                 if value:
244                     # if it's a multilink, split it
245                     if isinstance(prop, hyperdb.Multilink):
246                         value = value.split(':')
247                     elif isinstance(prop, hyperdb.Password):
248                         value = password.Password(value)
249                     elif isinstance(prop, hyperdb.Interval):
250                         value = date.Interval(value)
251                     elif isinstance(prop, hyperdb.Date):
252                         value = date.Date(value)
253                     elif isinstance(prop, hyperdb.Boolean):
254                         value = value.lower() in ('yes', 'true', 'on', '1')
255                     elif isinstance(prop, hyperdb.Number):
256                         value = float(value)
257                     d[name] = value
258                 elif exists:
259                     # nuke the existing value
260                     if isinstance(prop, hyperdb.Multilink):
261                         d[name] = []
262                     else:
263                         d[name] = None
265             # perform the edit
266             if exists:
267                 # edit existing
268                 cl.set(nodeid, **d)
269             else:
270                 # new node
271                 found[cl.create(**d)] = 1
273         # retire the removed entries
274         for nodeid in cl.list():
275             if not found.has_key(nodeid):
276                 cl.retire(nodeid)
278         # all OK
279         self.db.commit()
281         self.client.ok_message.append(_('Items edited OK'))
283 class _EditAction(Action):
284     def isEditingSelf(self):
285         """Check whether a user is editing his/her own details."""
286         return (self.nodeid == self.userid
287                 and self.db.user.get(self.nodeid, 'username') != 'anonymous')
289     def editItemPermission(self, props):
290         """Determine whether the user has permission to edit this item.
292         Base behaviour is to check the user can edit this class. If we're
293         editing the "user" class, users are allowed to edit their own details.
294         Unless it's the "roles" property, which requires the special Permission
295         "Web Roles".
296         """
297         if self.classname == 'user':
298             if props.has_key('roles') and not self.hasPermission('Web Roles'):
299                 raise Unauthorised, _("You do not have permission to edit user roles")
300             if self.isEditingSelf():
301                 return 1
302         if self.hasPermission('Edit'):
303             return 1
304         return 0
306     def newItemPermission(self, props):
307         """Determine whether the user has permission to create (edit) this item.
309         Base behaviour is to check the user can edit this class. No additional
310         property checks are made. Additionally, new user items may be created
311         if the user has the "Web Registration" Permission.
313         """
314         if (self.classname == 'user' and self.hasPermission('Web Registration')
315             or self.hasPermission('Edit')):
316             return 1
317         return 0
319     #
320     #  Utility methods for editing
321     #
322     def _editnodes(self, all_props, all_links, newids=None):
323         ''' Use the props in all_props to perform edit and creation, then
324             use the link specs in all_links to do linking.
325         '''
326         # figure dependencies and re-work links
327         deps = {}
328         links = {}
329         for cn, nodeid, propname, vlist in all_links:
330             if not all_props.has_key((cn, nodeid)):
331                 # link item to link to doesn't (and won't) exist
332                 continue
333             for value in vlist:
334                 if not all_props.has_key(value):
335                     # link item to link to doesn't (and won't) exist
336                     continue
337                 deps.setdefault((cn, nodeid), []).append(value)
338                 links.setdefault(value, []).append((cn, nodeid, propname))
340         # figure chained dependencies ordering
341         order = []
342         done = {}
343         # loop detection
344         change = 0
345         while len(all_props) != len(done):
346             for needed in all_props.keys():
347                 if done.has_key(needed):
348                     continue
349                 tlist = deps.get(needed, [])
350                 for target in tlist:
351                     if not done.has_key(target):
352                         break
353                 else:
354                     done[needed] = 1
355                     order.append(needed)
356                     change = 1
357             if not change:
358                 raise ValueError, 'linking must not loop!'
360         # now, edit / create
361         m = []
362         for needed in order:
363             props = all_props[needed]
364             if not props:
365                 # nothing to do
366                 continue
367             cn, nodeid = needed
369             if nodeid is not None and int(nodeid) > 0:
370                 # make changes to the node
371                 props = self._changenode(cn, nodeid, props)
373                 # and some nice feedback for the user
374                 if props:
375                     info = ', '.join(props.keys())
376                     m.append('%s %s %s edited ok'%(cn, nodeid, info))
377                 else:
378                     m.append('%s %s - nothing changed'%(cn, nodeid))
379             else:
380                 assert props
382                 # make a new node
383                 newid = self._createnode(cn, props)
384                 if nodeid is None:
385                     self.nodeid = newid
386                 nodeid = newid
388                 # and some nice feedback for the user
389                 m.append('%s %s created'%(cn, newid))
391             # fill in new ids in links
392             if links.has_key(needed):
393                 for linkcn, linkid, linkprop in links[needed]:
394                     props = all_props[(linkcn, linkid)]
395                     cl = self.db.classes[linkcn]
396                     propdef = cl.getprops()[linkprop]
397                     if not props.has_key(linkprop):
398                         if linkid is None or linkid.startswith('-'):
399                             # linking to a new item
400                             if isinstance(propdef, hyperdb.Multilink):
401                                 props[linkprop] = [newid]
402                             else:
403                                 props[linkprop] = newid
404                         else:
405                             # linking to an existing item
406                             if isinstance(propdef, hyperdb.Multilink):
407                                 existing = cl.get(linkid, linkprop)[:]
408                                 existing.append(nodeid)
409                                 props[linkprop] = existing
410                             else:
411                                 props[linkprop] = newid
413         return '<br>'.join(m)
415     def _changenode(self, cn, nodeid, props):
416         """Change the node based on the contents of the form."""
417         # check for permission
418         if not self.editItemPermission(props):
419             raise Unauthorised, 'You do not have permission to edit %s'%cn
421         # make the changes
422         cl = self.db.classes[cn]
423         return cl.set(nodeid, **props)
425     def _createnode(self, cn, props):
426         """Create a node based on the contents of the form."""
427         # check for permission
428         if not self.newItemPermission(props):
429             raise Unauthorised, 'You do not have permission to create %s'%cn
431         # create the node and return its id
432         cl = self.db.classes[cn]
433         return cl.create(**props)
435 class EditItemAction(_EditAction):
436     def lastUserActivity(self):
437         if self.form.has_key(':lastactivity'):
438             return date.Date(self.form[':lastactivity'].value)
439         elif self.form.has_key('@lastactivity'):
440             return date.Date(self.form['@lastactivity'].value)
441         else:
442             return None
444     def lastNodeActivity(self):
445         cl = getattr(self.client.db, self.classname)
446         return cl.get(self.nodeid, 'activity')
448     def detectCollision(self, userActivity, nodeActivity):
449         # Result from lastUserActivity may be None. If it is, assume there's no
450         # conflict, or at least not one we can detect.
451         if userActivity:
452             return userActivity < nodeActivity
454     def handleCollision(self):
455         self.client.template = 'collision'
457     def handle(self):
458         """Perform an edit of an item in the database.
460         See parsePropsFromForm and _editnodes for special variables.
462         """
463         if self.detectCollision(self.lastUserActivity(), self.lastNodeActivity()):
464             self.handleCollision()
465             return
467         props, links = self.client.parsePropsFromForm()
469         # handle the props
470         try:
471             message = self._editnodes(props, links)
472         except (ValueError, KeyError, IndexError), message:
473             self.client.error_message.append(_('Apply Error: ') + str(message))
474             return
476         # commit now that all the tricky stuff is done
477         self.db.commit()
479         # redirect to the item's edit page
480         # redirect to finish off
481         url = self.base + self.classname
482         # note that this action might have been called by an index page, so
483         # we will want to include index-page args in this URL too
484         if self.nodeid is not None:
485             url += self.nodeid
486         url += '?@ok_message=%s&@template=%s'%(urllib.quote(message),
487             urllib.quote(self.template))
488         if self.nodeid is None:
489             req = templating.HTMLRequest(self)
490             url += '&' + req.indexargs_href('', {})[1:]
491         raise Redirect, url
493 class NewItemAction(_EditAction):
494     def handle(self):
495         ''' Add a new item to the database.
497             This follows the same form as the EditItemAction, with the same
498             special form values.
499         '''
500         # parse the props from the form
501         try:
502             props, links = self.client.parsePropsFromForm(create=True)
503         except (ValueError, KeyError), message:
504             self.client.error_message.append(_('Error: ') + str(message))
505             return
507         # handle the props - edit or create
508         try:
509             # when it hits the None element, it'll set self.nodeid
510             messages = self._editnodes(props, links)
512         except (ValueError, KeyError, IndexError), message:
513             # these errors might just be indicative of user dumbness
514             self.client.error_message.append(_('Error: ') + str(message))
515             return
517         # commit now that all the tricky stuff is done
518         self.db.commit()
520         # redirect to the new item's page
521         raise Redirect, '%s%s%s?@ok_message=%s&@template=%s'%(self.base,
522             self.classname, self.nodeid, urllib.quote(messages),
523             urllib.quote(self.template))
525 class PassResetAction(Action):
526     def handle(self):
527         """Handle password reset requests.
529         Presence of either "name" or "address" generates email. Presence of
530         "otk" performs the reset.
532         """
533         if self.form.has_key('otk'):
534             # pull the rego information out of the otk database
535             otk = self.form['otk'].value
536             uid = self.db.otks.get(otk, 'uid')
537             if uid is None:
538                 self.client.error_message.append("""Invalid One Time Key!
539 (a Mozilla bug may cause this message to show up erroneously,
540  please check your email)""")
541                 return
543             # re-open the database as "admin"
544             if self.user != 'admin':
545                 self.client.opendb('admin')
546                 self.db = self.client.db
548             # change the password
549             newpw = password.generatePassword()
551             cl = self.db.user
552 # XXX we need to make the "default" page be able to display errors!
553             try:
554                 # set the password
555                 cl.set(uid, password=password.Password(newpw))
556                 # clear the props from the otk database
557                 self.db.otks.destroy(otk)
558                 self.db.commit()
559             except (ValueError, KeyError), message:
560                 self.client.error_message.append(str(message))
561                 return
563             # user info
564             address = self.db.user.get(uid, 'address')
565             name = self.db.user.get(uid, 'username')
567             # send the email
568             tracker_name = self.db.config.TRACKER_NAME
569             subject = 'Password reset for %s'%tracker_name
570             body = '''
571 The password has been reset for username "%(name)s".
573 Your password is now: %(password)s
574 '''%{'name': name, 'password': newpw}
575             if not self.client.standard_message([address], subject, body):
576                 return
578             self.client.ok_message.append('Password reset and email sent to %s' %
579                                           address)
580             return
582         # no OTK, so now figure the user
583         if self.form.has_key('username'):
584             name = self.form['username'].value
585             try:
586                 uid = self.db.user.lookup(name)
587             except KeyError:
588                 self.client.error_message.append('Unknown username')
589                 return
590             address = self.db.user.get(uid, 'address')
591         elif self.form.has_key('address'):
592             address = self.form['address'].value
593             uid = uidFromAddress(self.db, ('', address), create=0)
594             if not uid:
595                 self.client.error_message.append('Unknown email address')
596                 return
597             name = self.db.user.get(uid, 'username')
598         else:
599             self.client.error_message.append('You need to specify a username '
600                 'or address')
601             return
603         # generate the one-time-key and store the props for later
604         otk = ''.join([random.choice(chars) for x in range(32)])
605         d = {'uid': uid, self.db.otks.timestamp: time.time()}
606         self.db.otks.set(otk, **d)
608         # send the email
609         tracker_name = self.db.config.TRACKER_NAME
610         subject = 'Confirm reset of password for %s'%tracker_name
611         body = '''
612 Someone, perhaps you, has requested that the password be changed for your
613 username, "%(name)s". If you wish to proceed with the change, please follow
614 the link below:
616   %(url)suser?@template=forgotten&@action=passrst&otk=%(otk)s
618 You should then receive another email with the new password.
619 '''%{'name': name, 'tracker': tracker_name, 'url': self.base, 'otk': otk}
620         if not self.client.standard_message([address], subject, body):
621             return
623         self.client.ok_message.append('Email sent to %s'%address)
625 class ConfRegoAction(Action):
626     def handle(self):
627         """Grab the OTK, use it to load up the new user details."""
628         try:
629             # pull the rego information out of the otk database
630             self.userid = self.db.confirm_registration(self.form['otk'].value)
631         except (ValueError, KeyError), message:
632             self.client.error_message.append(str(message))
633             return
635         # log the new user in
636         self.client.user = self.db.user.get(self.userid, 'username')
637         # re-open the database for real, using the user
638         self.client.opendb(self.client.user)
640         # if we have a session, update it
641         if hasattr(self, 'session'):
642             self.client.db.sessions.set(self.session, user=self.user,
643                 last_use=time.time())
644         else:
645             # new session cookie
646             self.client.set_cookie(self.user)
648         # nice message
649         message = _('You are now registered, welcome!')
650         url = '%suser%s?@ok_message=%s'%(self.base, self.userid,
651             urllib.quote(message))
653         # redirect to the user's page (but not 302, as some email clients seem
654         # to want to reload the page, or something)
655         return '''<html><head><title>%s</title></head>
656             <body><p><a href="%s">%s</a></p>
657             <script type="text/javascript">
658             window.setTimeout('window.location = "%s"', 1000);
659             </script>'''%(message, url, message, url)
661 class RegisterAction(Action):
662     name = 'register'
663     permissionType = 'Web Registration'
665     def handle(self):
666         """Attempt to create a new user based on the contents of the form
667         and then set the cookie.
669         Return 1 on successful login.
670         """
671         props = self.client.parsePropsFromForm(create=True)[0][('user', None)]
673         # registration isn't allowed to supply roles
674         if props.has_key('roles'):
675             raise Unauthorised, _("It is not permitted to supply roles "
676                 "at registration.")
678         username = props['username']
679         try:
680             self.db.user.lookup(username)
681             self.client.error_message.append(_('Error: A user with the '
682                 'username "%(username)s" already exists')%props)
683             return
684         except KeyError:
685             pass
687         # generate the one-time-key and store the props for later
688         otk = ''.join([random.choice(chars) for x in range(32)])
689         for propname, proptype in self.db.user.getprops().items():
690             value = props.get(propname, None)
691             if value is None:
692                 pass
693             elif isinstance(proptype, hyperdb.Date):
694                 props[propname] = str(value)
695             elif isinstance(proptype, hyperdb.Interval):
696                 props[propname] = str(value)
697             elif isinstance(proptype, hyperdb.Password):
698                 props[propname] = str(value)
699         props[self.db.otks.timestamp] = time.time()
700         self.db.otks.set(otk, **props)
702         # send the email
703         tracker_name = self.db.config.TRACKER_NAME
704         tracker_email = self.db.config.TRACKER_EMAIL
705         subject = 'Complete your registration to %s -- key %s'%(tracker_name,
706                                                                   otk)
707         body = """To complete your registration of the user "%(name)s" with
708 %(tracker)s, please do one of the following:
710 - send a reply to %(tracker_email)s and maintain the subject line as is (the
711 reply's additional "Re:" is ok),
713 - or visit the following URL:
715 %(url)s?@action=confrego&otk=%(otk)s
716 """ % {'name': props['username'], 'tracker': tracker_name, 'url': self.base,
717         'otk': otk, 'tracker_email': tracker_email}
718         if not self.client.standard_message([props['address']], subject, body,
719         tracker_email):
720             return
722         # commit changes to the database
723         self.db.commit()
725         # redirect to the "you're almost there" page
726         raise Redirect, '%suser?@template=rego_progress'%self.base
728 class LogoutAction(Action):
729     def handle(self):
730         """Make us really anonymous - nuke the cookie too."""
731         # log us out
732         self.client.make_user_anonymous()
734         # construct the logout cookie
735         now = Cookie._getdate()
736         self.client.additional_headers['Set-Cookie'] = \
737            '%s=deleted; Max-Age=0; expires=%s; Path=%s;'%(self.client.cookie_name,
738             now, self.client.cookie_path)
740         # Let the user know what's going on
741         self.client.ok_message.append(_('You are logged out'))
743 class LoginAction(Action):
744     def handle(self):
745         """Attempt to log a user in.
747         Sets up a session for the user which contains the login credentials.
749         """
750         # we need the username at a minimum
751         if not self.form.has_key('__login_name'):
752             self.client.error_message.append(_('Username required'))
753             return
755         # get the login info
756         self.client.user = self.form['__login_name'].value
757         if self.form.has_key('__login_password'):
758             password = self.form['__login_password'].value
759         else:
760             password = ''
762         # make sure the user exists
763         try:
764             self.client.userid = self.db.user.lookup(self.client.user)
765         except KeyError:
766             name = self.client.user
767             self.client.error_message.append(_('No such user "%(name)s"')%locals())
768             self.client.make_user_anonymous()
769             return
771         # verify the password
772         if not self.verifyPassword(self.client.userid, password):
773             self.client.make_user_anonymous()
774             self.client.error_message.append(_('Incorrect password'))
775             return
777         # Determine whether the user has permission to log in.
778         # Base behaviour is to check the user has "Web Access".
779         if not self.hasPermission("Web Access"):
780             self.client.make_user_anonymous()
781             self.client.error_message.append(_("You do not have permission to login"))
782             return
784         # now we're OK, re-open the database for real, using the user
785         self.client.opendb(self.client.user)
787         # set the session cookie
788         self.client.set_cookie(self.client.user)
790     def verifyPassword(self, userid, password):
791         ''' Verify the password that the user has supplied
792         '''
793         stored = self.db.user.get(self.client.userid, 'password')
794         if password == stored:
795             return 1
796         if not password and not stored:
797             return 1
798         return 0