Skip to content

Commit 9253a10

Browse files
author
Richard Jones
committed
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.
1 parent 8a61988 commit 9253a10

File tree

15 files changed

+342
-308
lines changed

15 files changed

+342
-308
lines changed

CHANGES.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,13 @@ Feature:
1717
- roundup-server options -g and -u accept both ids and names (sf bug 983769)
1818
- roundup-server now has a configuration file (-C option)
1919
- added mod_python interface (see installation.txt)
20+
- reorganised tracker configuration, using ConfigParser config, cleaned-up
21+
schema definition and implementing easier extension writing (sf rfe 661301)
22+
- Permissions may now be defined on a per-property basis
23+
- added "Create" Permission. Replaces the "Web"- and "Email Registration"
24+
Permissions.
25+
- added option to turn off registration confirmation via email
26+
("instant_registration" in config) (sf rfe 922209)
2027

2128

2229
2004-??-?? 0.7.7

demo.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
#
33
# Copyright (c) 2003 Richard Jones ([email protected])
44
#
5-
# $Id: demo.py,v 1.15 2004-07-27 11:36:01 a1s Exp $
5+
# $Id: demo.py,v 1.16 2004-07-28 02:29:45 richard Exp $
66

77
import sys, os, string, re, urlparse, ConfigParser
88
import shutil, socket, errno, BaseHTTPServer
@@ -59,6 +59,7 @@ def install_demo(home, backend):
5959
config['TRACKER_WEB'] = 'http://%s:%s/demo/'%(hostname, port)
6060

6161
# write the config
62+
config['INSTANT_REGISTRATION'] = 1
6263
config.save()
6364

6465
# open the tracker and initialise

roundup/backends/back_anydbm.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
# BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
1616
# SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
1717
#
18-
#$Id: back_anydbm.py,v 1.169 2004-07-27 04:28:39 richard Exp $
18+
#$Id: back_anydbm.py,v 1.170 2004-07-28 02:29:45 richard Exp $
1919
'''This module defines a backend that saves the hyperdatabase in a
2020
database chosen by anydbm. It is guaranteed to always be available in python
2121
versions >2.1.1 (the dumbdbm fallback in 2.1.1 and earlier has several
@@ -144,6 +144,8 @@ def addclass(self, cl):
144144
self.classes[cn] = cl
145145

146146
# add default Edit and View permissions
147+
self.security.addPermission(name="Create", klass=cn,
148+
description="User is allowed to create "+cn)
147149
self.security.addPermission(name="Edit", klass=cn,
148150
description="User is allowed to edit "+cn)
149151
self.security.addPermission(name="View", klass=cn,

roundup/backends/back_metakit.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# $Id: back_metakit.py,v 1.82 2004-07-27 00:57:18 richard Exp $
1+
# $Id: back_metakit.py,v 1.83 2004-07-28 02:29:45 richard Exp $
22
'''Metakit backend for Roundup, originally by Gordon McMillan.
33
44
Known Current Bugs:
@@ -186,6 +186,8 @@ def addclass(self, cl):
186186
self.tables.append(name=cl.classname)
187187

188188
# add default Edit and View permissions
189+
self.security.addPermission(name="Create", klass=cn,
190+
description="User is allowed to create "+cn)
189191
self.security.addPermission(name="Edit", klass=cl.classname,
190192
description="User is allowed to edit "+cl.classname)
191193
self.security.addPermission(name="View", klass=cl.classname,

roundup/backends/rdbms_common.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# $Id: rdbms_common.py,v 1.126 2004-07-27 04:28:39 richard Exp $
1+
# $Id: rdbms_common.py,v 1.127 2004-07-28 02:29:45 richard Exp $
22
''' Relational database (SQL) backend common code.
33
44
Basics:
@@ -607,6 +607,8 @@ def addclass(self, cl):
607607
self.classes[cn] = cl
608608

609609
# add default Edit and View permissions
610+
self.security.addPermission(name="Create", klass=cn,
611+
description="User is allowed to create "+cn)
610612
self.security.addPermission(name="Edit", klass=cn,
611613
description="User is allowed to edit "+cn)
612614
self.security.addPermission(name="View", klass=cn,

roundup/cgi/actions.py

Lines changed: 102 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
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 $
22

33
import re, cgi, StringIO, urllib, Cookie, time, random
44

@@ -53,10 +53,13 @@ def permission(self):
5353
raise Unauthorised, self._('You do not have permission to '
5454
'%(action)s the %(classname)s class.')%info
5555

56-
def hasPermission(self, permission):
56+
_marker = []
57+
def hasPermission(self, permission, classname=_marker):
5758
"""Check whether the user has 'permission' on the current class."""
59+
if classname is self._marker:
60+
classname = self.client.classname
5861
return self.db.security.hasPermission(permission, self.client.userid,
59-
self.client.classname)
62+
classname)
6063

6164
def gettext(self, msgid):
6265
"""Return the localized translation of msgid"""
@@ -314,46 +317,8 @@ def handle(self):
314317

315318
self.client.ok_message.append(self._('Items edited OK'))
316319

317-
class _EditAction(Action):
318-
def isEditingSelf(self):
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-
#
320+
class EditCommon:
321+
'''Utility methods for editing.'''
357322
def _editnodes(self, all_props, all_links, newids=None):
358323
''' Use the props in all_props to perform edit and creation, then
359324
use the link specs in all_links to do linking.
@@ -475,7 +440,38 @@ def _createnode(self, cn, props):
475440
cl = self.db.classes[cn]
476441
return cl.create(**props)
477442

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):
479475
def lastUserActivity(self):
480476
if self.form.has_key(':lastactivity'):
481477
d = date.Date(self.form[':lastactivity'].value)
@@ -539,7 +535,7 @@ def handle(self):
539535
url += '&' + req.indexargs_href('', {})[1:]
540536
raise Redirect, url
541537

542-
class NewItemAction(_EditAction):
538+
class NewItemAction(EditCommon, Action):
543539
def handle(self):
544540
''' Add a new item to the database.
545541
@@ -677,28 +673,21 @@ def handle(self):
677673

678674
self.client.ok_message.append(self._('Email sent to %s') % address)
679675

680-
class ConfRegoAction(Action):
681-
def handle(self):
682-
"""Grab the OTK, use it to load up the new user details."""
683-
try:
684-
# pull the rego information out of the otk database
685-
self.userid = self.db.confirm_registration(self.form['otk'].value)
686-
except (ValueError, KeyError), message:
687-
self.client.error_message.append(str(message))
688-
return
689-
676+
class RegoCommon:
677+
def finishRego(self):
690678
# log the new user in
691-
self.client.user = self.db.user.get(self.userid, 'username')
679+
self.client.userid = self.userid
680+
user = self.client.user = self.db.user.get(self.userid, 'username')
692681
# re-open the database for real, using the user
693-
self.client.opendb(self.client.user)
682+
self.client.opendb(user)
694683

695684
# 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())
685+
if hasattr(self.client, 'session'):
686+
self.client.db.getSessionManager().set(self.client.session,
687+
user=user, last_use=time.time())
699688
else:
700689
# new session cookie
701-
self.client.set_cookie(self.user)
690+
self.client.set_cookie(user)
702691

703692
# nice message
704693
message = self._('You are now registered, welcome!')
@@ -713,48 +702,80 @@ def handle(self):
713702
window.setTimeout('window.location = "%s"', 1000);
714703
</script>'''%(message, url, message, url)
715704

716-
class RegisterAction(Action):
705+
class ConfRegoAction(RegoCommon, Action):
706+
def handle(self):
707+
"""Grab the OTK, use it to load up the new user details."""
708+
try:
709+
# pull the rego information out of the otk database
710+
self.userid = self.db.confirm_registration(self.form['otk'].value)
711+
except (ValueError, KeyError), message:
712+
self.client.error_message.append(str(message))
713+
return
714+
self.finishRego()
715+
716+
class RegisterAction(RegoCommon, EditCommon, Action):
717717
name = 'register'
718-
permissionType = 'Web Registration'
718+
permissionType = 'Create'
719719

720720
def handle(self):
721721
"""Attempt to create a new user based on the contents of the form
722722
and then set the cookie.
723723
724724
Return 1 on successful login.
725725
"""
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
727733

728734
# 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'):
730737
raise Unauthorised, self._(
731738
"It is not permitted to supply roles at registration.")
732739

733-
username = props['username']
734-
try:
735-
self.db.user.lookup(username)
736-
self.client.error_message.append(self._('Error: A user with the '
737-
'username "%(username)s" already exists')%props)
738-
return
739-
except KeyError:
740-
pass
740+
# skip the confirmation step?
741+
if self.db.config['INSTANT_REGISTRATION']:
742+
# handle the create now
743+
try:
744+
# when it hits the None element, it'll set self.nodeid
745+
messages = self._editnodes(props, links)
746+
except (ValueError, KeyError, IndexError, exceptions.Reject), \
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()
741762

742763
# generate the one-time-key and store the props for later
743764
for propname, proptype in self.db.user.getprops().items():
744-
value = props.get(propname, None)
765+
value = user_props.get(propname, None)
745766
if value is None:
746767
pass
747768
elif isinstance(proptype, hyperdb.Date):
748-
props[propname] = str(value)
769+
user_props[propname] = str(value)
749770
elif isinstance(proptype, hyperdb.Interval):
750-
props[propname] = str(value)
771+
user_props[propname] = str(value)
751772
elif isinstance(proptype, hyperdb.Password):
752-
props[propname] = str(value)
773+
user_props[propname] = str(value)
753774
otks = self.db.getOTKManager()
754775
otk = ''.join([random.choice(chars) for x in range(32)])
755776
while otks.exists(otk):
756777
otk = ''.join([random.choice(chars) for x in range(32)])
757-
otks.set(otk, **props)
778+
otks.set(otk, **user_props)
758779

759780
# send the email
760781
tracker_name = self.db.config.TRACKER_NAME
@@ -771,9 +792,9 @@ def handle(self):
771792
772793
%(url)s?@action=confrego&otk=%(otk)s
773794
774-
""" % {'name': props['username'], 'tracker': tracker_name, 'url': self.base,
775-
'otk': otk, 'tracker_email': tracker_email}
776-
if not self.client.standard_message([props['address']], subject,
795+
""" % {'name': user_props['username'], 'tracker': tracker_name,
796+
'url': self.base, 'otk': otk, 'tracker_email': tracker_email}
797+
if not self.client.standard_message([user_props['address']], subject,
777798
body, (tracker_name, tracker_email)):
778799
return
779800

roundup/cgi/client.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# $Id: client.py,v 1.185 2004-07-27 02:30:31 richard Exp $
1+
# $Id: client.py,v 1.186 2004-07-28 02:29:45 richard Exp $
22

33
"""WWW request handler (also used in the stand-alone server).
44
"""
@@ -22,8 +22,6 @@ def initialiseSecurity(security):
2222
This function is directly invoked by security.Security.__init__()
2323
as a part of the Security object instantiation.
2424
'''
25-
security.addPermission(name="Web Registration",
26-
description="User may register through the web")
2725
p = security.addPermission(name="Web Access",
2826
description="User may access the web interface")
2927
security.addPermissionToRole('Admin', p)
@@ -398,6 +396,9 @@ def determine_user(self):
398396
# make sure the anonymous user is valid if we're using it
399397
if user == 'anonymous':
400398
self.make_user_anonymous()
399+
if not self.db.security.hasPermission('Web Access', self.userid):
400+
raise Unauthorised, self._("Anonymous users are not "
401+
"allowed to use the web interface")
401402
else:
402403
self.user = user
403404

0 commit comments

Comments
 (0)