comparison roundup/cgi/actions.py @ 2649:1df7d4a41da4

Buncha stuff (sorry about the large checkin): - Permissions may now be defined on a per-property basis - added "Create" Permission. Replaces the "Web"- and "Email Registration" Permissions. - added option to turn off registration confirmation via email ("instant_registration" in config) Migrated the user edit/view permission to use check code. Fixed a buncha stuff in the default templates. Needs a thorough review though.
author Richard Jones <richard@users.sourceforge.net>
date Wed, 28 Jul 2004 02:29:46 +0000
parents 5a8d9465827e
children 534f78a2e400
comparison
equal deleted inserted replaced
2648:fe71e108d998 2649:1df7d4a41da4
1 #$Id: actions.py,v 1.35 2004-07-20 02:07:58 richard Exp $ 1 #$Id: actions.py,v 1.36 2004-07-28 02:29:45 richard Exp $
2 2
3 import re, cgi, StringIO, urllib, Cookie, time, random 3 import re, cgi, StringIO, urllib, Cookie, time, random
4 4
5 from roundup import hyperdb, token, date, password, rcsv, exceptions 5 from roundup import hyperdb, token, date, password, rcsv, exceptions
6 from roundup.i18n import _ 6 from roundup.i18n import _
51 not self.hasPermission(self.permissionType)): 51 not self.hasPermission(self.permissionType)):
52 info = {'action': self.name, 'classname': self.classname} 52 info = {'action': self.name, 'classname': self.classname}
53 raise Unauthorised, self._('You do not have permission to ' 53 raise Unauthorised, self._('You do not have permission to '
54 '%(action)s the %(classname)s class.')%info 54 '%(action)s the %(classname)s class.')%info
55 55
56 def hasPermission(self, permission): 56 _marker = []
57 def hasPermission(self, permission, classname=_marker):
57 """Check whether the user has 'permission' on the current class.""" 58 """Check whether the user has 'permission' on the current class."""
59 if classname is self._marker:
60 classname = self.client.classname
58 return self.db.security.hasPermission(permission, self.client.userid, 61 return self.db.security.hasPermission(permission, self.client.userid,
59 self.client.classname) 62 classname)
60 63
61 def gettext(self, msgid): 64 def gettext(self, msgid):
62 """Return the localized translation of msgid""" 65 """Return the localized translation of msgid"""
63 return self.client.translator.gettext(msgid) 66 return self.client.translator.gettext(msgid)
64 67
312 # all OK 315 # all OK
313 self.db.commit() 316 self.db.commit()
314 317
315 self.client.ok_message.append(self._('Items edited OK')) 318 self.client.ok_message.append(self._('Items edited OK'))
316 319
317 class _EditAction(Action): 320 class EditCommon:
318 def isEditingSelf(self): 321 '''Utility methods for editing.'''
319 """Check whether a user is editing his/her own details."""
320 return (self.nodeid == self.userid
321 and self.db.user.get(self.nodeid, 'username') != 'anonymous')
322
323 def editItemPermission(self, props):
324 """Determine whether the user has permission to edit this item.
325
326 Base behaviour is to check the user can edit this class. If we're
327 editing the "user" class, users are allowed to edit their own details.
328 Unless it's the "roles" property, which requires the special Permission
329 "Web Roles".
330 """
331 if self.classname == 'user':
332 if props.has_key('roles') and not self.hasPermission('Web Roles'):
333 raise Unauthorised, self._(
334 "You do not have permission to edit user roles")
335 if self.isEditingSelf():
336 return 1
337 if self.hasPermission('Edit'):
338 return 1
339 return 0
340
341 def newItemPermission(self, props):
342 """Determine whether the user has permission to create (edit) this item.
343
344 Base behaviour is to check the user can edit this class. No additional
345 property checks are made. Additionally, new user items may be created
346 if the user has the "Web Registration" Permission.
347
348 """
349 if (self.classname == 'user' and self.hasPermission('Web Registration')
350 or self.hasPermission('Edit')):
351 return 1
352 return 0
353
354 #
355 # Utility methods for editing
356 #
357 def _editnodes(self, all_props, all_links, newids=None): 322 def _editnodes(self, all_props, all_links, newids=None):
358 ''' Use the props in all_props to perform edit and creation, then 323 ''' Use the props in all_props to perform edit and creation, then
359 use the link specs in all_links to do linking. 324 use the link specs in all_links to do linking.
360 ''' 325 '''
361 # figure dependencies and re-work links 326 # figure dependencies and re-work links
473 438
474 # create the node and return its id 439 # create the node and return its id
475 cl = self.db.classes[cn] 440 cl = self.db.classes[cn]
476 return cl.create(**props) 441 return cl.create(**props)
477 442
478 class EditItemAction(_EditAction): 443 def isEditingSelf(self):
444 """Check whether a user is editing his/her own details."""
445 return (self.nodeid == self.userid
446 and self.db.user.get(self.nodeid, 'username') != 'anonymous')
447
448 def editItemPermission(self, props):
449 """Determine whether the user has permission to edit this item.
450
451 Base behaviour is to check the user can edit this class. If we're
452 editing the "user" class, users are allowed to edit their own details.
453 Unless it's the "roles" property, which requires the special Permission
454 "Web Roles".
455 """
456 if self.classname == 'user':
457 if props.has_key('roles') and not self.hasPermission('Web Roles'):
458 raise Unauthorised, self._(
459 "You do not have permission to edit user roles")
460 if self.isEditingSelf():
461 return 1
462 if self.hasPermission('Edit'):
463 return 1
464 return 0
465
466 def newItemPermission(self, props):
467 """Determine whether the user has permission to create this item.
468
469 Base behaviour is to check the user can edit this class. No additional
470 property checks are made.
471 """
472 return self.hasPermission('Create', self.classname)
473
474 class EditItemAction(EditCommon, Action):
479 def lastUserActivity(self): 475 def lastUserActivity(self):
480 if self.form.has_key(':lastactivity'): 476 if self.form.has_key(':lastactivity'):
481 d = date.Date(self.form[':lastactivity'].value) 477 d = date.Date(self.form[':lastactivity'].value)
482 elif self.form.has_key('@lastactivity'): 478 elif self.form.has_key('@lastactivity'):
483 d = date.Date(self.form['@lastactivity'].value) 479 d = date.Date(self.form['@lastactivity'].value)
537 if self.nodeid is None: 533 if self.nodeid is None:
538 req = templating.HTMLRequest(self.client) 534 req = templating.HTMLRequest(self.client)
539 url += '&' + req.indexargs_href('', {})[1:] 535 url += '&' + req.indexargs_href('', {})[1:]
540 raise Redirect, url 536 raise Redirect, url
541 537
542 class NewItemAction(_EditAction): 538 class NewItemAction(EditCommon, Action):
543 def handle(self): 539 def handle(self):
544 ''' Add a new item to the database. 540 ''' Add a new item to the database.
545 541
546 This follows the same form as the EditItemAction, with the same 542 This follows the same form as the EditItemAction, with the same
547 special form values. 543 special form values.
675 if not self.client.standard_message([address], subject, body): 671 if not self.client.standard_message([address], subject, body):
676 return 672 return
677 673
678 self.client.ok_message.append(self._('Email sent to %s') % address) 674 self.client.ok_message.append(self._('Email sent to %s') % address)
679 675
680 class ConfRegoAction(Action): 676 class RegoCommon:
677 def finishRego(self):
678 # log the new user in
679 self.client.userid = self.userid
680 user = self.client.user = self.db.user.get(self.userid, 'username')
681 # re-open the database for real, using the user
682 self.client.opendb(user)
683
684 # if we have a session, update it
685 if hasattr(self.client, 'session'):
686 self.client.db.getSessionManager().set(self.client.session,
687 user=user, last_use=time.time())
688 else:
689 # new session cookie
690 self.client.set_cookie(user)
691
692 # nice message
693 message = self._('You are now registered, welcome!')
694 url = '%suser%s?@ok_message=%s'%(self.base, self.userid,
695 urllib.quote(message))
696
697 # redirect to the user's page (but not 302, as some email clients seem
698 # to want to reload the page, or something)
699 return '''<html><head><title>%s</title></head>
700 <body><p><a href="%s">%s</a></p>
701 <script type="text/javascript">
702 window.setTimeout('window.location = "%s"', 1000);
703 </script>'''%(message, url, message, url)
704
705 class ConfRegoAction(RegoCommon, Action):
681 def handle(self): 706 def handle(self):
682 """Grab the OTK, use it to load up the new user details.""" 707 """Grab the OTK, use it to load up the new user details."""
683 try: 708 try:
684 # pull the rego information out of the otk database 709 # pull the rego information out of the otk database
685 self.userid = self.db.confirm_registration(self.form['otk'].value) 710 self.userid = self.db.confirm_registration(self.form['otk'].value)
686 except (ValueError, KeyError), message: 711 except (ValueError, KeyError), message:
687 self.client.error_message.append(str(message)) 712 self.client.error_message.append(str(message))
688 return 713 return
689 714 self.finishRego()
690 # log the new user in 715
691 self.client.user = self.db.user.get(self.userid, 'username') 716 class RegisterAction(RegoCommon, EditCommon, Action):
692 # re-open the database for real, using the user
693 self.client.opendb(self.client.user)
694
695 # if we have a session, update it
696 if hasattr(self, 'session'):
697 self.client.db.sessions.set(self.session, user=self.user,
698 last_use=time.time())
699 else:
700 # new session cookie
701 self.client.set_cookie(self.user)
702
703 # nice message
704 message = self._('You are now registered, welcome!')
705 url = '%suser%s?@ok_message=%s'%(self.base, self.userid,
706 urllib.quote(message))
707
708 # redirect to the user's page (but not 302, as some email clients seem
709 # to want to reload the page, or something)
710 return '''<html><head><title>%s</title></head>
711 <body><p><a href="%s">%s</a></p>
712 <script type="text/javascript">
713 window.setTimeout('window.location = "%s"', 1000);
714 </script>'''%(message, url, message, url)
715
716 class RegisterAction(Action):
717 name = 'register' 717 name = 'register'
718 permissionType = 'Web Registration' 718 permissionType = 'Create'
719 719
720 def handle(self): 720 def handle(self):
721 """Attempt to create a new user based on the contents of the form 721 """Attempt to create a new user based on the contents of the form
722 and then set the cookie. 722 and then set the cookie.
723 723
724 Return 1 on successful login. 724 Return 1 on successful login.
725 """ 725 """
726 props = self.client.parsePropsFromForm(create=1)[0][('user', None)] 726 # parse the props from the form
727 try:
728 props, links = self.client.parsePropsFromForm(create=1)
729 except (ValueError, KeyError), message:
730 self.client.error_message.append(self._('Error: %s')
731 % str(message))
732 return
727 733
728 # registration isn't allowed to supply roles 734 # registration isn't allowed to supply roles
729 if props.has_key('roles'): 735 user_props = props[('user', None)]
736 if user_props.has_key('roles'):
730 raise Unauthorised, self._( 737 raise Unauthorised, self._(
731 "It is not permitted to supply roles at registration.") 738 "It is not permitted to supply roles at registration.")
732 739
733 username = props['username'] 740 # skip the confirmation step?
734 try: 741 if self.db.config['INSTANT_REGISTRATION']:
735 self.db.user.lookup(username) 742 # handle the create now
736 self.client.error_message.append(self._('Error: A user with the ' 743 try:
737 'username "%(username)s" already exists')%props) 744 # when it hits the None element, it'll set self.nodeid
738 return 745 messages = self._editnodes(props, links)
739 except KeyError: 746 except (ValueError, KeyError, IndexError, exceptions.Reject), \
740 pass 747 message:
748 # these errors might just be indicative of user dumbness
749 self.client.error_message.append(_('Error: %s') % str(message))
750 return
751
752 # fix up the initial roles
753 self.db.user.set(self.nodeid,
754 roles=self.db.config['NEW_WEB_USER_ROLES'])
755
756 # commit now that all the tricky stuff is done
757 self.db.commit()
758
759 # finish off by logging the user in
760 self.userid = self.nodeid
761 return self.finishRego()
741 762
742 # generate the one-time-key and store the props for later 763 # generate the one-time-key and store the props for later
743 for propname, proptype in self.db.user.getprops().items(): 764 for propname, proptype in self.db.user.getprops().items():
744 value = props.get(propname, None) 765 value = user_props.get(propname, None)
745 if value is None: 766 if value is None:
746 pass 767 pass
747 elif isinstance(proptype, hyperdb.Date): 768 elif isinstance(proptype, hyperdb.Date):
748 props[propname] = str(value) 769 user_props[propname] = str(value)
749 elif isinstance(proptype, hyperdb.Interval): 770 elif isinstance(proptype, hyperdb.Interval):
750 props[propname] = str(value) 771 user_props[propname] = str(value)
751 elif isinstance(proptype, hyperdb.Password): 772 elif isinstance(proptype, hyperdb.Password):
752 props[propname] = str(value) 773 user_props[propname] = str(value)
753 otks = self.db.getOTKManager() 774 otks = self.db.getOTKManager()
754 otk = ''.join([random.choice(chars) for x in range(32)]) 775 otk = ''.join([random.choice(chars) for x in range(32)])
755 while otks.exists(otk): 776 while otks.exists(otk):
756 otk = ''.join([random.choice(chars) for x in range(32)]) 777 otk = ''.join([random.choice(chars) for x in range(32)])
757 otks.set(otk, **props) 778 otks.set(otk, **user_props)
758 779
759 # send the email 780 # send the email
760 tracker_name = self.db.config.TRACKER_NAME 781 tracker_name = self.db.config.TRACKER_NAME
761 tracker_email = self.db.config.TRACKER_EMAIL 782 tracker_email = self.db.config.TRACKER_EMAIL
762 subject = 'Complete your registration to %s -- key %s'%(tracker_name, 783 subject = 'Complete your registration to %s -- key %s'%(tracker_name,
769 790
770 - or visit the following URL: 791 - or visit the following URL:
771 792
772 %(url)s?@action=confrego&otk=%(otk)s 793 %(url)s?@action=confrego&otk=%(otk)s
773 794
774 """ % {'name': props['username'], 'tracker': tracker_name, 'url': self.base, 795 """ % {'name': user_props['username'], 'tracker': tracker_name,
775 'otk': otk, 'tracker_email': tracker_email} 796 'url': self.base, 'otk': otk, 'tracker_email': tracker_email}
776 if not self.client.standard_message([props['address']], subject, 797 if not self.client.standard_message([user_props['address']], subject,
777 body, (tracker_name, tracker_email)): 798 body, (tracker_name, tracker_email)):
778 return 799 return
779 800
780 # commit changes to the database 801 # commit changes to the database
781 self.db.commit() 802 self.db.commit()

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