Mercurial > p > roundup > code
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() |
