summary | shortlog | log | commit | commitdiff | tree
raw | patch | inline | side by side (parent: e4f4d91)
raw | patch | inline | side by side (parent: e4f4d91)
author | richard <richard@57a73879-2fb5-44c3-a270-3262357dd7e2> | |
Fri, 26 Jul 2002 08:27:00 +0000 (08:27 +0000) | ||
committer | richard <richard@57a73879-2fb5-44c3-a270-3262357dd7e2> | |
Fri, 26 Jul 2002 08:27:00 +0000 (08:27 +0000) |
templates have been migrated to that setup. Lots of unit tests. Still some
issue in the web form for editing Roles assigned to users.
git-svn-id: http://svn.roundup-tracker.org/svnroot/roundup/trunk@921 57a73879-2fb5-44c3-a270-3262357dd7e2
issue in the web form for editing Roles assigned to users.
git-svn-id: http://svn.roundup-tracker.org/svnroot/roundup/trunk@921 57a73879-2fb5-44c3-a270-3262357dd7e2
21 files changed:
diff --git a/COPYING.txt b/COPYING.txt
index 330c6f7085766db427c5a39273173bb8a267c909..db92559a13e33ddb961d4aed5e05707edf3f8a35 100644 (file)
--- a/COPYING.txt
+++ b/COPYING.txt
-Copyright (c) 2001 Bizar Software Pty Ltd (http://www.bizarsoftware.com.au/)
Copyright (c) 2002 eKit.com Inc (http://www.ekit.com/)
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+Copyright (c) 2001 Bizar Software Pty Ltd (http://www.bizarsoftware.com.au/)
+
This module is free software, and you may redistribute it and/or modify
under the same terms as Python, so long as this copyright message and
disclaimer are retained in their original form.
BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
-
The stylesheet included with this package has been copied from the Zope
management interface and presumably belongs to Digital Creations.
diff --git a/doc/security.txt b/doc/security.txt
index 2d3748a77cd188222108714d379994344504868e..66f9f7eb83f473729c8ce852fe0440f9fc4f9fed 100644 (file)
--- a/doc/security.txt
+++ b/doc/security.txt
Security Mechanisms
===================
-:Version: $Revision: 1.12 $
+:Version: $Revision: 1.13 $
Current situation
=================
base roles (for admin user).
'''
- def hasClassPermission(self, db, classname, permission, userid):
+ def hasPermission(self, db, classname, permission, userid):
''' Look through all the Roles, and hence Permissions, and see if
"permission" is there for the specified classname.
ei = db.security.addPermission(name="Edit", klass="issue",
description="User is allowed to edit issues")
db.security.addPermissionToRole('User', ei)
- ai = db.security.addPermission(name="Assign", klass="issue",
- description="User may be assigned to issues")
- db.security.addPermissionToRole('User', ei)
+ ai = db.security.addPermission(name="View", klass="issue",
+ description="User is allowed to access issues")
+ db.security.addPermissionToRole('User', ai)
In the dbinit ``init()``::
+ # create the two default users
r = db.getclass('role').lookup('Admin')
user.create(username="admin", password=Password(adminpw),
- address=instance_config.ADMIN_EMAIL, roles=[r])
-
- # choose your anonymous user access permission here
- #r = db.getclass('role').lookup('No Rego')
- r = db.getclass('role').lookup('User')
- user.create(username="anonymous", roles=[r])
+ address=instance_config.ADMIN_EMAIL, roles='Admin')
+ r = db.getclass('role').lookup('Anonymous')
+ user.create(username="anonymous", roles='Anonymous')
-Then in the code that matters, calls to ``hasClassPermission`` and
+Then in the code that matters, calls to ``hasPermission`` and
``hasNodePermission`` are made to determine if the user has permission
to perform some action::
- if db.security.hasClassPermission('issue', 'Edit', userid):
+ if db.security.hasPermission('issue', 'Edit', userid):
# all ok
if db.security.hasNodePermission('issue', nodeid, assignedto=userid):
where:
- the permission attribute gives a comma-separated list of permission names.
- These are checked in turn using ``hasClassPermission`` and requires one to
+ These are checked in turn using ``hasPermission`` and requires one to
be OK.
- the other attributes are lookups on the node using ``hasNodePermission``. If
the attribute value is "$userid" then the current user's userid is tested.
A set of Permissions are built in to the security module by default:
- Edit (everything)
-- Access (everything)
-- Assign (everything)
+- View (everything)
The default interfaces define:
These are hooked into the default Roles:
-- Admin (Edit everything, Access everything, Assign everything)
+- Admin (Edit everything, View everything)
- User ()
- Anonymous (Web Registration, Email Registration)
gets the "Anonymous" assigned when the database is initialised on installation.
The two default schemas then define:
-- Edit issue, Access issue (both)
-- Edit support, Access support (extended only)
+- Edit issue, View issue (both)
+- Edit file, View file (both)
+- Edit msg, View msg (both)
+- Edit support, View support (extended only)
+
+and assign those Permissions to the "User" Role. New users are assigned the
+Roles defined in the config file as:
-and assign those Permissions to the "User" Role.
+- NEW_WEB_USER_ROLES
+- NEW_EMAIL_USER_ROLES
Authentication of Users
- implement htmltemplate tests on permissions
- switch all code over from using config vars for permission checks to using
permissions
+ - change all explicit admin user checks for Role checks
- include config vars for initial Roles for anonymous web, new web and new
email users
diff --git a/doc/upgrading.txt b/doc/upgrading.txt
index e768d857f384a2d79b8460ee9e761722451391b3..2fb2159025c62c2931182ba06a66024af8f5b310 100644 (file)
--- a/doc/upgrading.txt
+++ b/doc/upgrading.txt
reindexing
TODO: dbinit now imports classes from selct_db
TODO: select_db needs fixing to include Class, FileClass and IssueClass
+TODO: migration of security settings
Migrating from 0.4.1 to 0.4.2
index 128453ff67575d67f11b1ded6dfd1912d8d3759a..3b85d788a954f1b3023b9b93a533d781d2e68007 100644 (file)
# BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
# SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
#
-#$Id: back_anydbm.py,v 1.53 2002-07-25 07:14:06 richard Exp $
+#$Id: back_anydbm.py,v 1.54 2002-07-26 08:26:59 richard Exp $
'''
This module defines a backend that saves the hyperdatabase in a database
chosen by anydbm. It is guaranteed to always be available in python
if node.has_key(self.db.RETIRED_FLAG):
continue
for key, value in requirements.items():
- if node[key] and node[key].lower() != value:
+ if node[key] is None or node[key].lower() != value:
break
else:
l.append(nodeid)
#
#$Log: not supported by cvs2svn $
+#Revision 1.53 2002/07/25 07:14:06 richard
+#Bugger it. Here's the current shape of the new security implementation.
+#Still to do:
+# . call the security funcs from cgi and mailgw
+# . change shipped templates to include correct initialisation and remove
+# the old config vars
+#... that seems like a lot. The bulk of the work has been done though. Honest :)
+#
#Revision 1.52 2002/07/19 03:36:34 richard
#Implemented the destroy() method needed by the session database (and possibly
#others). At the same time, I removed the leading underscores from the hyperdb
diff --git a/roundup/cgi_client.py b/roundup/cgi_client.py
index 9574d95690cf0df269752160c3af5fc5e4974c41..99a65f15c2693acc8f3bfbedab70ba7178bb81de 100644 (file)
--- a/roundup/cgi_client.py
+++ b/roundup/cgi_client.py
# BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
# SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
#
-# $Id: cgi_client.py,v 1.144 2002-07-25 07:14:05 richard Exp $
+# $Id: cgi_client.py,v 1.145 2002-07-26 08:26:59 richard Exp $
__doc__ = """
WWW request handler (also used in the stand-alone server).
This function is directly invoked by security.Security.__init__()
as a part of the Security object instantiation.
'''
- newid = security.addPermission(name="Web Registration",
+ security.addPermission(name="Web Registration",
description="User may register through the web")
- security.addPermissionToRole('Anonymous', newid)
+
+ # doing Role stuff through the web - make sure Admin can
+ p = security.addPermission(name="Web Roles",
+ description="User may manipulate user Roles through the web")
+ security.addPermissionToRole('Admin', p)
class Client:
'''
err = _("sanity check: unknown user name `%s'")%self.user
raise Unauthorised, errmsg
- def header(self, headers=None):
+ def header(self, headers=None, response=200):
'''Put up the appropriate header.
'''
if headers is None:
headers = {'Content-Type':'text/html'}
if not headers.has_key('Content-Type'):
headers['Content-Type'] = 'text/html'
- self.request.send_response(200)
+ self.request.send_response(response)
for entry in headers.items():
self.request.send_header(*entry)
self.request.end_headers()
style = open(os.path.join(self.instance.TEMPLATES, 'style.css')).read()
# figure who the user is
- user_name = self.user or ''
- if user_name not in ('', 'anonymous'):
- userid = self.db.user.lookup(self.user)
- else:
- userid = None
+ user_name = self.user
+ userid = self.db.user.lookup(user_name)
# figure all the header links
if hasattr(self.instance, 'HEADER_INDEX_LINKS'):
links = []
for name in self.instance.HEADER_INDEX_LINKS:
spec = getattr(self.instance, name + '_INDEX')
- # skip if we need to fill in the logged-in user id there's
- # no user logged in
+ # skip if we need to fill in the logged-in user id and
+ # we're anonymous
if (spec['FILTERSPEC'].has_key('assignedto') and
spec['FILTERSPEC']['assignedto'] in ('CURRENT USER',
- None) and userid is None):
+ None) and user_name == 'anonymous'):
continue
links.append(self.make_index_link(name))
else:
_('Unassigned <a href="issue?assignedto=-1&status=-1,unread,deferred,chatting,need-eg,in-progress,testing,done-cbb&:sort=-activity&:filter=status,assignedto&:columns=id,activity,status,title,assignedto&:group=priority&show_customization=1">Issues</a>')
]
- if userid:
+ user_info = _('<a href="login">Login</a>')
+ add_links = ''
+ if user_name != 'anonymous':
# add any personal queries to the menu
try:
queries = self.db.getclass('query')
except KeyError:
# no query class
- queries = self.instance.dbinit.Class(self.db,
- "query",
- klass=hyperdb.String(),
- name=hyperdb.String(),
- url=hyperdb.String())
+ queries = self.instance.dbinit.Class(self.db, "query",
+ klass=hyperdb.String(), name=hyperdb.String(),
+ url=hyperdb.String())
queries.setkey('name')
-#queries.disableJournalling()
+ #queries.disableJournalling()
try:
qids = self.db.getclass('user').get(userid, 'queries')
except KeyError, e:
#self.db.getclass('user').addprop(queries=hyperdb.Multilink('query'))
qids = []
for qid in qids:
- links.append('<a href=%s?%s>%s</a>' % (queries.get(qid, 'klass'),
- queries.get(qid, 'url'),
- queries.get(qid, 'name')))
-
- # if they're logged in, include links to their information, and the
- # ability to add an issue
- if user_name not in ('', 'anonymous'):
+ links.append('<a href=%s?%s>%s</a>'%(queries.get(qid, 'klass'),
+ queries.get(qid, 'url'), queries.get(qid, 'name')))
+
+ # if they're logged in, include links to their information,
+ # and the ability to add an issue
user_info = _('''
<a href="user%(userid)s">My Details</a> | <a href="logout">Logout</a>
''')%locals()
- # figure the "add class" links
- if hasattr(self.instance, 'HEADER_ADD_LINKS'):
- classes = self.instance.HEADER_ADD_LINKS
- else:
- classes = ['issue']
- l = []
- for class_name in classes:
- cap_class = class_name.capitalize()
- links.append(_('Add <a href="new%(class_name)s">'
- '%(cap_class)s</a>')%locals())
-
- # if there's no config header link spec, force a user link here
- if not hasattr(self.instance, 'HEADER_INDEX_LINKS'):
- links.append(_('<a href="issue?assignedto=%(userid)s&status=-1,unread,chatting,open,pending&:filter=status,resolution,assignedto&:sort=-activity&:columns=id,activity,status,resolution,title,creator&:group=type&show_customization=1">My Issues</a>')%locals())
+
+ # figure the "add class" links
+ if hasattr(self.instance, 'HEADER_ADD_LINKS'):
+ classes = self.instance.HEADER_ADD_LINKS
else:
- user_info = _('<a href="login">Login</a>')
- add_links = ''
+ classes = ['issue']
+ l = []
+ for class_name in classes:
+ # make sure the user has permission to add
+ if not self.db.security.hasPermission('Edit', userid, class_name):
+ continue
+ cap_class = class_name.capitalize()
+ links.append(_('Add <a href="new%(class_name)s">'
+ '%(cap_class)s</a>')%locals())
- # if the user is admin, include admin links
+ # if the user can edit everything, include the links
admin_links = ''
- if user_name == 'admin':
+ userid = self.db.user.lookup(user_name)
+ if self.db.security.hasPermission('Edit', userid):
links.append(_('<a href="list_classes">Class List</a>'))
- links.append(_('<a href="user?:sort=username">User List</a>'))
+ links.append(_('<a href="user?:sort=username&:group=roles">User List</a>'))
links.append(_('<a href="newuser">Add User</a>'))
# add the search links
classes = ['issue']
l = []
for class_name in classes:
+ # make sure the user has permission to view
+ if not self.db.security.hasPermission('View', userid, class_name):
+ continue
cap_class = class_name.capitalize()
links.append(_('Search <a href="search%(class_name)s">'
'%(cap_class)s</a>')%locals())
# XXX deviates from spec - loses the '+' (that's a reserved character
# in URLS
def list(self, sort=None, group=None, filter=None, columns=None,
- filterspec=None, show_customization=None, show_nodes=1, pagesize=None):
+ filterspec=None, show_customization=None, show_nodes=1,
+ pagesize=None):
''' call the template index with the args
:sort - sort by prop name, optionally preceeded with '-'
'''Display a basic edit page that allows simple editing of the
nodes of the current class
'''
- if self.user != 'admin':
+ userid = self.db.user.lookup(self.user)
+ if not self.db.security.hasPermission('Edit', userid):
raise Unauthorised
w = self.write
cn = self.classname
idlessprops = cl.getprops(protected=0).keys()
props = ['id'] + idlessprops
-
# get the CSV module
try:
import csv
# extract the new values
d = {}
for name, value in zip(idlessprops, values):
- d[name] = value.strip()
+ value = value.strip()
+ # only add the property if it has a value
+ if value:
+ # if it's a multilink, split it
+ if isinstance(cl.properties[name], hyperdb.Multilink):
+ value = value.split(':')
+ d[name] = value
# perform the edit
if cl.hasnode(nodeid):
cl.retire(nodeid)
w(_('''<p class="form-help">You may edit the contents of the
- "%(classname)s" class using this form. The lines are full-featured
- Comma-Separated-Value lines, so you may include commas and even
+ "%(classname)s" class using this form. Commas, newlines and double
+ quotes (") must be handled delicately. You may include commas and
newlines by enclosing the values in double-quotes ("). Double
quotes themselves must be quoted by doubling ("").</p>
+ <p class="form-help">Multilink properties have their multiple
+ values colon (":") separated (... ,"one:two:three", ...)</p>
<p class="form-help">Remove entries by deleting their line. Add
new entries by appending
them to the table - put an X in the id column.</p>''')%{'classname':cn})
for nodeid in cl.list():
l = []
for name in props:
- l.append(cgi.escape(str(cl.get(nodeid, name))))
+ value = cl.get(nodeid, name)
+ if value is None:
+ l.append('')
+ elif isinstance(value, type([])):
+ l.append(cgi.escape(':'.join(map(str, value))))
+ else:
+ l.append(cgi.escape(str(cl.get(nodeid, name))))
w(p.join(l) + '\n')
w(_('</textarea><br><input type="submit" value="Save Changes"></form>'))
node's id. The node id will be appended to the multilink.
'''
cn = self.classname
+ userid = self.db.user.lookup(self.user)
+ if not self.db.security.hasPermission('View', userid, cn):
+ raise Unauthorised
cl = self.db.classes[cn]
if self.form.has_key(':multilink'):
link = self.form[':multilink'].value
# possibly perform a create
keys = self.form.keys()
if [i for i in keys if i[0] != ':']:
+ # no dice if you can't edit!
+ if not self.db.security.hasPermission('Edit', userid, cn):
+ raise Unauthorised
props = {}
try:
nid = self._createnode()
Don't do any of the message or file handling, just create the node.
'''
+ userid = self.db.user.lookup(self.user)
+ if not self.db.security.hasPermission('Edit', userid, 'user'):
+ raise Unauthorised
+
cn = self.classname
cl = self.db.classes[cn]
This form works very much the same way as newnode - it just has a
file upload.
'''
+ userid = self.db.user.lookup(self.user)
+ if not self.db.security.hasPermission('Edit', userid, 'file'):
+ raise Unauthorised
cn = self.classname
cl = self.db.classes[cn]
props = parsePropsFromForm(self.db, cl, self.form)
'''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 == '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):
+ # ok, so we need to be able to edit everything, or be this node's
+ # user
+ userid = self.db.user.lookup(self.user)
+ if (not self.db.security.hasPermission('Edit', userid)
+ and self.user != node_user):
raise Unauthorised
#
self.header(headers={'Content-Type': mime_type})
self.write(cl.get(nodeid, 'content'))
+ def permission(self):
+ '''
+ '''
+
def classes(self, message=None):
''' display a list of all the classes in the database
'''
- if self.user == 'admin':
- self.pagehead(_('Table of classes'), message)
- classnames = self.db.classes.keys()
- classnames.sort()
- self.write('<table border=0 cellspacing=0 cellpadding=2>\n')
- for cn in classnames:
- cl = self.db.getclass(cn)
- self.write('<tr class="list-header"><th colspan=2 align=left>'
- '<a href="%s">%s</a></th></tr>'%(cn, cn.capitalize()))
- for key, value in cl.properties.items():
- if value is None: value = ''
- else: value = str(value)
- self.write('<tr><th align=left>%s</th><td>%s</td></tr>'%(
- key, cgi.escape(value)))
- self.write('</table>')
- self.pagefoot()
- else:
+ userid = self.db.user.lookup(self.user)
+ if not self.db.security.hasPermission('Edit', userid):
raise Unauthorised
+ self.pagehead(_('Table of classes'), message)
+ classnames = self.db.classes.keys()
+ classnames.sort()
+ self.write('<table border=0 cellspacing=0 cellpadding=2>\n')
+ for cn in classnames:
+ cl = self.db.getclass(cn)
+ self.write('<tr class="list-header"><th colspan=2 align=left>'
+ '<a href="%s">%s</a></th></tr>'%(cn, cn.capitalize()))
+ for key, value in cl.properties.items():
+ if value is None: value = ''
+ else: value = str(value)
+ self.write('<tr><th align=left>%s</th><td>%s</td></tr>'%(
+ key, cgi.escape(value)))
+ self.write('</table>')
+ self.pagefoot()
+
def login(self, message=None, newuser_form=None, action='index'):
'''Display a login page.
'''
<td><input type="submit" value="Log In"></td></tr>
</form>
''')%locals())
- if self.user is None and self.instance.ANONYMOUS_REGISTER == 'deny':
+ userid = self.db.user.lookup(self.user)
+ if not self.db.security.hasPermission('Web Registration', userid):
self.write('</table>')
self.pagefoot()
return
return 1 on successful login
'''
+ # make sure we're allowed to register
+ userid = self.db.user.lookup(self.user)
+ if not self.db.security.hasPermission('Web Registration', userid):
+ raise Unauthorised
+
# re-open the database as "admin"
self.opendb('admin')
cl = self.db.user
try:
props = parsePropsFromForm(self.db, cl, self.form)
+ props['roles'] = self.instance.NEW_WEB_USER_ROLES
uid = cl.create(**props)
+ self.db.commit()
except ValueError, message:
action = self.form['__destination_url'].value
self.login(message, action=action)
else:
session = session[:-1]
- print 'session set to', `session`
-
# insert the session in the sessiondb
sessions = self.db.getclass('__sessions')
self.session = sessions.create(sessid=session, user=user,
session, expire, path)})
def make_user_anonymous(self):
- # make us anonymous if we can
- try:
- self.db.user.lookup('anonymous')
- self.user = 'anonymous'
- except KeyError:
- self.user = None
+ ''' Make use anonymous
+
+ This method used to handle non-existence of the 'anonymous'
+ user, but that user is mandatory now.
+ '''
+ self.db.user.lookup('anonymous')
+ self.user = 'anonymous'
def logout(self, message=None):
self.make_user_anonymous()
sessions.disableJournalling()
def main(self):
+ ''' Wrap the request and handle unauthorised requests
+ '''
+ self.desired_action = None
+ try:
+ self.main_action()
+ except Unauthorised:
+ self.header(response=403)
+ if self.desired_action is None or self.desired_action == 'login':
+ self.login() # go to the index after login
+ else:
+ self.login(action=self.desired_action)
+
+ def main_action(self):
'''Wrap the database accesses so we can close the database cleanly
'''
# determine the uid to use
self.db.commit()
user = sessions.get(sessid, 'user')
+ # sanity check on the user still being valid
+ try:
+ self.db.user.lookup(user)
+ except KeyError:
+ user = 'anonymous'
+
# make sure the anonymous user is valid if we're using it
if user == 'anonymous':
self.make_user_anonymous()
action = 'index'
else:
action = path[0]
+ self.desired_action = action
# Everthing ignores path[1:]
# - The file download link generator actually relies on this - it
# allow anonymous people to register
if action == 'newuser_action':
- # if we don't have a login and anonymous people aren't allowed to
- # register, then spit up the login form
- if self.instance.ANONYMOUS_REGISTER == 'deny' and self.user is None:
- if action == 'login':
- self.login() # go to the index after login
- else:
- self.login(action=action)
- return
# try to add the user
if not self.newuser_action():
return
if not action:
action = 'index'
- # no login or registration, make sure totally anonymous access is OK
- elif self.instance.ANONYMOUS_ACCESS == 'deny' and self.user is None:
- if action == 'login':
- self.login() # go to the index after login
- else:
- self.login(action=action)
- return
-
# re-open the database for real, using the user
self.opendb(self.user)
#
# $Log: not supported by cvs2svn $
+# Revision 1.144 2002/07/25 07:14:05 richard
+# Bugger it. Here's the current shape of the new security implementation.
+# Still to do:
+# . call the security funcs from cgi and mailgw
+# . change shipped templates to include correct initialisation and remove
+# the old config vars
+# ... that seems like a lot. The bulk of the work has been done though. Honest :)
+#
# Revision 1.143 2002/07/20 19:29:10 gmcm
# Fixes/improvements to the search form & saved queries.
#
index 2558f3750f42edae51cc119c319903534433fbc8..56074fa14c3849e7c903139d92b77aadc38eb696 100644 (file)
--- a/roundup/htmltemplate.py
+++ b/roundup/htmltemplate.py
# BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
# SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
#
-# $Id: htmltemplate.py,v 1.104 2002-07-25 07:14:05 richard Exp $
+# $Id: htmltemplate.py,v 1.105 2002-07-26 08:26:59 richard Exp $
__doc__ = """
Template engine.
if d.has_key('permission'):
l.remove(('permission', d['permission']))
for value in d['permission'].split(','):
- if security.hasClassPermission(self.classname, value, userid):
+ if security.hasPermission(value, userid, self.classname):
# just passing the permission is OK
return self.execute_template(ok)
old_group = this_group
# display this node's row
- w(replace.execute_template(template))
+ self.nodeid = nodeid
+ w(self.execute_template(template))
if matches:
self.node_matches(matches[nodeid], len(columns))
self.nodeid = None
#
# $Log: not supported by cvs2svn $
+# Revision 1.104 2002/07/25 07:14:05 richard
+# Bugger it. Here's the current shape of the new security implementation.
+# Still to do:
+# . call the security funcs from cgi and mailgw
+# . change shipped templates to include correct initialisation and remove
+# the old config vars
+# ... that seems like a lot. The bulk of the work has been done though. Honest :)
+#
# Revision 1.103 2002/07/20 19:29:10 gmcm
# Fixes/improvements to the search form & saved queries.
#
diff --git a/roundup/mailgw.py b/roundup/mailgw.py
index 88868fd39cfa789d5333183e0de60eed58013f59..a1c21ac82da78bfa4ec7dc39131ee57f315baea9 100644 (file)
--- a/roundup/mailgw.py
+++ b/roundup/mailgw.py
an exception, the original message is bounced back to the sender with the
explanatory message given in the exception.
-$Id: mailgw.py,v 1.78 2002-07-25 07:14:06 richard Exp $
+$Id: mailgw.py,v 1.79 2002-07-26 08:26:59 richard Exp $
'''
class MailUsageHelp(Exception):
pass
-class UnAuthorized(Exception):
+class Unauthorized(Exception):
""" Access denied """
def initialiseSecurity(security):
'''
newid = security.addPermission(name="Email Registration",
description="Anonymous may register through e-mail")
- security.addPermissionToRole('Anonymous', newid)
class Message(mimetools.Message):
''' subclass mimetools.Message so we can retrieve the parts of the
m.append('\n\nMail Gateway Help\n=================')
m.append(fulldoc)
m = self.bounce_message(message, sendto, m)
- except UnAuthorized, value:
+ except Unauthorized, value:
# just inform the user that he is not authorized
sendto = [sendto[0][1]]
m = ['']
# handle the users
#
- # Don't create users if ANONYMOUS_REGISTER_MAIL is denied
- # ... fall back on ANONYMOUS_REGISTER if the other doesn't exist
+ # Don't create users if anonymous isn't allowed to register
create = 1
- if hasattr(self.instance, 'ANONYMOUS_REGISTER_MAIL'):
- if self.instance.ANONYMOUS_REGISTER_MAIL == 'deny':
- create = 0
- elif self.instance.ANONYMOUS_REGISTER == 'deny':
+ anonid = self.db.user.lookup('anonymous')
+ if not self.db.security.hasPermission('Email Registration', anonid):
create = 0
- author = self.db.uidFromAddress(message.getaddrlist('from')[0],
+ author = uidFromAddress(self.db, message.getaddrlist('from')[0],
create=create)
if not author:
- raise UnAuthorized, '''
+ raise Unauthorized, '''
You are not a registered user.
Unknown address: %s
# look up the recipient - create if necessary (and we're
# allowed to)
- recipient = self.db.uidFromAddress(recipient, create)
+ recipient = uidFromAddress(self.db, recipient, create)
# if all's well, add the recipient to the list
if recipient:
return nodeid
+def extractUserFromList(userClass, users):
+ '''Given a list of users, try to extract the first non-anonymous user
+ and return that user, otherwise return None
+ '''
+ if len(users) > 1:
+ for user in users:
+ # make sure we don't match the anonymous or admin user
+ if userClass.get(user, 'username') in ('admin', 'anonymous'):
+ continue
+ # first valid match will do
+ return user
+ # well, I guess we have no choice
+ return user[0]
+ elif users:
+ return users[0]
+ return None
+
+def uidFromAddress(db, address, create=1):
+ ''' address is from the rfc822 module, and therefore is (name, addr)
+
+ user is created if they don't exist in the db already
+ '''
+ (realname, address) = address
+
+ # try a straight match of the address
+ user = extractUserFromList(db.user, db.user.stringFind(address=address))
+ if user is not None: return user
+
+ # try the user alternate addresses if possible
+ props = db.user.getprops()
+ if props.has_key('alternate_addresses'):
+ users = db.user.filter(None, {'alternate_addresses': address},
+ [], [])
+ user = extractUserFromList(db.user, users)
+ if user is not None: return user
+
+ # try to match the username to the address (for local
+ # submissions where the address is empty)
+ user = extractUserFromList(db.user, db.user.stringFind(username=address))
+
+ # couldn't match address or username, so create a new user
+ if create:
+ return db.user.create(username=address, address=address,
+ realname=realname, roles=db.config.NEW_EMAIL_USER_ROLES)
+ else:
+ return 0
+
def parseContent(content, keep_citations, keep_body,
blank_line=re.compile(r'[\r\n]+\s*[\r\n]+'),
eol=re.compile(r'[\r\n]+'),
#
# $Log: not supported by cvs2svn $
+# Revision 1.78 2002/07/25 07:14:06 richard
+# Bugger it. Here's the current shape of the new security implementation.
+# Still to do:
+# . call the security funcs from cgi and mailgw
+# . change shipped templates to include correct initialisation and remove
+# the old config vars
+# ... that seems like a lot. The bulk of the work has been done though. Honest :)
+#
# Revision 1.77 2002/07/18 11:17:31 gmcm
# Add Number and Boolean types to hyperdb.
# Add conversion cases to web, mail & admin interfaces.
diff --git a/roundup/roundupdb.py b/roundup/roundupdb.py
index 57e678da6ed9cbc74b4a2d7b412913e93d0c21e8..1b2a385551b31cfaefac752acdca7b1e3bcb8456 100644 (file)
--- a/roundup/roundupdb.py
+++ b/roundup/roundupdb.py
# BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
# SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
#
-# $Id: roundupdb.py,v 1.62 2002-07-14 02:05:53 richard Exp $
+# $Id: roundupdb.py,v 1.63 2002-07-26 08:26:59 richard Exp $
__doc__ = """
Extending hyperdb with types specific to issue-tracking.
# this var must contain a file to write the mail to
SENDMAILDEBUG = os.environ.get('SENDMAILDEBUG', '')
-
-def extractUserFromList(userClass, users):
- '''Given a list of users, try to extract the first non-anonymous user
- and return that user, otherwise return None
- '''
- if len(users) > 1:
- # make sure we don't match the anonymous or admin user
- for user in users:
- if user == '1': continue
- if userClass.get(user, 'username') == 'anonymous': continue
- # first valid match will do
- return user
- # well, I guess we have no choice
- return user[0]
- elif users:
- return users[0]
- return None
-
class Database:
def getuid(self):
"""Return the id of the "user" node associated with the user
that owns this connection to the hyperdatabase."""
return self.user.lookup(self.journaltag)
- def uidFromAddress(self, address, create=1):
- ''' address is from the rfc822 module, and therefore is (name, addr)
-
- user is created if they don't exist in the db already
- '''
- (realname, address) = address
-
- # try a straight match of the address
- user = extractUserFromList(self.user,
- self.user.stringFind(address=address))
- if user is not None: return user
-
- # try the user alternate addresses if possible
- props = self.user.getprops()
- if props.has_key('alternate_addresses'):
- users = self.user.filter(None, {'alternate_addresses': address},
- [], [])
- user = extractUserFromList(self.user, users)
- if user is not None: return user
-
- # try to match the username to the address (for local
- # submissions where the address is empty)
- user = extractUserFromList(self.user,
- self.user.stringFind(username=address))
-
- # couldn't match address or username, so create a new user
- if create:
- return self.user.create(username=address, address=address,
- realname=realname)
- else:
- return 0
-
class MessageSendError(RuntimeError):
pass
#
# $Log: not supported by cvs2svn $
+# Revision 1.62 2002/07/14 02:05:53 richard
+# . all storage-specific code (ie. backend) is now implemented by the backends
+#
# Revision 1.61 2002/07/09 04:19:09 richard
# Added reindex command to roundup-admin.
# Fixed reindex on first access.
diff --git a/roundup/security.py b/roundup/security.py
index 7475ca62eb9067272f1088094522ee4cee47a452..49e447795014c40af6a5b2c521dca2fa5acfd127 100644 (file)
--- a/roundup/security.py
+++ b/roundup/security.py
ee = self.addPermission(name="Edit",
description="User may edit everthing")
self.addPermissionToRole('Admin', ee)
- ae = self.addPermission(name="Access",
+ ae = self.addPermission(name="View",
description="User may access everything")
self.addPermissionToRole('Admin', ae)
- ae = self.addPermission(name="Assign",
- description="User may be assigned to anything")
- self.addPermissionToRole('Admin', ae)
reg = self.addPermission(name="Register Web",
description="User may register through the web")
- self.addPermissionToRole('Anonymous', reg)
reg = self.addPermission(name="Register Email",
description="User may register through the email")
- self.addPermissionToRole('Anonymous', reg)
# initialise the permissions and roles needed for the UIs
from roundup import cgi_client, mailgw
cgi_client.initialiseSecurity(self)
mailgw.initialiseSecurity(self)
- def hasClassPermission(self, classname, permission, userid):
+ def getPermission(self, permission, classname=None):
+ ''' Find the Permission matching the name and for the class, if the
+ classname is specified.
+
+ Raise ValueError if there is no exact match.
+ '''
+ perm = self.db.permission
+ for permissionid in perm.stringFind(name=permission):
+ klass = perm.get(permissionid, 'klass')
+ if classname is not None and classname == klass:
+ return permissionid
+ elif not classname and not klass:
+ return permissionid
+ if not classname:
+ raise ValueError, 'No permission "%s" defined'%permission
+ raise ValueError, 'No permission "%s" defined for "%s"'%(permission,
+ classname)
+
+ def hasPermission(self, permission, userid, classname=None):
''' Look through all the Roles, and hence Permissions, and see if
"permission" is there for the specified classname.
'''
roles = self.db.user.get(userid, 'roles')
- for roleid in roles:
+ if roles is None:
+ return 0
+ for rolename in roles.split(','):
+ if not rolename:
+ continue
+ roleid = self.db.role.lookup(rolename)
for permissionid in self.db.role.get(roleid, 'permissions'):
if self.db.permission.get(permissionid, 'name') != permission:
continue
index 1edc2dd1bf73fdfbe68a4026caf1d330692a1703..7c2153ddf5e5c2ada0db13b21c82fe4cdee4ac09 100644 (file)
# BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
# SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
#
-# $Id: dbinit.py,v 1.20 2002-07-17 12:39:10 gmcm Exp $
+# $Id: dbinit.py,v 1.21 2002-07-26 08:26:59 richard Exp $
import os
url=String())
query.setkey("name")
+ # Note: roles is a comma-separated string of Role names
user = Class(db, "user",
username=String(), password=Password(),
address=String(), realname=String(),
phone=String(), organisation=String(),
- alternate_addresses=String(), queries=Multilink("query"))
+ alternate_addresses=String(),
+ queries=Multilink('query'), roles=String())
user.setkey("username")
# FileClass automatically gets these properties:
assignedto=Link("user"), topic=Multilink("keyword"),
priority=Link("priority"), status=Link("status"))
+ #
+ # SECURITY SETTINGS
+ #
+ # new permissions for this schema
+ for cl in 'issue', 'file', 'msg':
+ db.security.addPermission(name="Edit", klass=cl,
+ description="User is allowed to edit "+cl)
+ db.security.addPermission(name="View", klass=cl,
+ description="User is allowed to access "+cl)
+
+ # Assign the appropriate permissions to the anonymous user's Anonymous
+ # Role. Choices here are:
+ # - Allow anonymous users to register through the web
+ p = db.security.getPermission('Web Registration')
+ db.security.addPermissionToRole('Anonymous', p)
+ # - Allow anonymous (new) users to register through the email gateway
+ p = db.security.getPermission('Email Registration')
+ db.security.addPermissionToRole('Anonymous', p)
+ # - Allow anonymous users access to the "issue" class of data
+ # Note: this also grants access to related information like files,
+ # messages, statuses etc that are linked to issues
+ #p = db.security.getPermission('View', 'issue')
+ #db.security.addPermissionToRole('Anonymous', p)
+ # - Allow anonymous users access to edit the "issue" class of data
+ # Note: this also grants access to create related information like
+ # files and messages etc that are linked to issues
+ #p = db.security.getPermission('Edit', 'issue')
+ #db.security.addPermissionToRole('Anonymous', p)
+
+ # Assign the access and edit permissions for issue, file and message
+ # to regular users now
+ for cl in 'issue', 'file', 'msg':
+ p = db.security.getPermission('View', cl)
+ db.security.addPermissionToRole('User', p)
+ p = db.security.getPermission('Edit', cl)
+ db.security.addPermissionToRole('User', p)
+
import detectors
detectors.init(db)
db = open("admin")
db.clear()
+ #
+ # INITIAL PRIORITY AND STATUS VALUES
+ #
pri = db.getclass('priority')
pri.create(name="critical", order="1")
pri.create(name="urgent", order="2")
stat.create(name="done-cbb", order="7")
stat.create(name="resolved", order="8")
+ # create the two default users
user = db.getclass('user')
- user.create(username="admin", password=adminpw,
- address=instance_config.ADMIN_EMAIL)
+ user.create(username="admin", password=adminpw,
+ address=instance_config.ADMIN_EMAIL, roles='Admin')
+ user.create(username="anonymous", roles='Anonymous')
+
db.commit()
#
# $Log: not supported by cvs2svn $
+# Revision 1.20 2002/07/17 12:39:10 gmcm
+# Saving, running & editing queries.
+#
# Revision 1.19 2002/07/14 02:05:54 richard
# . all storage-specific code (ie. backend) is now implemented by the backends
#
index e4a4eacfbf2fbd53d94e51ba7e825343fb6a3af1..f4ab59447bef9a4aff2e53c0f404fdeb0aec0c70 100644 (file)
-<!-- $Id: user.item,v 1.4 2002-07-17 12:39:11 gmcm Exp $-->
+<!-- $Id: user.item,v 1.5 2002-07-26 08:27:00 richard Exp $-->
<table border=0 cellspacing=0 cellpadding=2>
<tr class="strong-header">
<td width=1% nowrap align=right><span class="form-label">Login Password</span></td>
<td class="form-text"><display call="field('password', size=10)"></td>
</tr>
+<require permission="Web Roles">
+ <tr bgcolor="ffffea">
+ <td width=1% nowrap align=right><span class="form-label">Roles</span></td>
+ <td class="form-text"><display call="field('roles', size=10)"></td>
+ </tr>
+</require>
<tr bgcolor="ffffea">
<td width=1% nowrap align=right><span class="form-label">Phone</span></td>
<td class="form-text"><display call="field('phone', size=40)"></td>
diff --git a/roundup/templates/classic/instance_config.py b/roundup/templates/classic/instance_config.py
index cd06f426ff99d3268d29a074304bd17d5a790442..7b3e9548071b787e72e8a4ee5b738a762fdcd93a 100644 (file)
# BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
# SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
#
-# $Id: instance_config.py,v 1.18 2002-05-25 07:16:25 rochecompaan Exp $
+# $Id: instance_config.py,v 1.19 2002-07-26 08:26:59 richard Exp $
MAIL_DOMAIN=MAILHOST=HTTP_HOST=None
HTTP_PORT=0
# Where to place the web filtering HTML on the index page
FILTER_POSITION = 'bottom' # one of 'top', 'bottom', 'top and bottom'
-# Deny or allow anonymous access to the web interface
-ANONYMOUS_ACCESS = 'deny' # either 'deny' or 'allow'
-
-# Deny or allow anonymous users to register through the web interface
-ANONYMOUS_REGISTER = 'deny' # either 'deny' or 'allow'
-
-# Deny or allow anonymous users to register through the mail interface
-ANONYMOUS_REGISTER_MAIL = 'deny' # either 'deny' or 'allow'
+#
+# SECURITY DEFINITIONS
+#
+# define the Roles that a user gets when they register with the tracker
+# these are a comma-separated string of role names (e.g. 'Admin,User')
+NEW_WEB_USER_ROLES = 'User'
+NEW_EMAIL_USER_ROLES = 'User'
# Send nosy messages to the author of the message
MESSAGES_TO_AUTHOR = 'no' # either 'yes' or 'no'
#
# $Log: not supported by cvs2svn $
+# Revision 1.18 2002/05/25 07:16:25 rochecompaan
+# Merged search_indexing-branch with HEAD
+#
# Revision 1.17 2002/05/22 00:32:33 richard
# . changed the default message list in issues to display the message body
# . made backends.__init__ be more specific about which ImportErrors it really
index 0ada216bde71f11f1f8e46f6ec901c0630149145..6149c4dc5d8d9bf26408b4eb568703357dd85134 100644 (file)
# BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
# SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
#
-# $Id: dbinit.py,v 1.23 2002-07-14 02:05:54 richard Exp $
+# $Id: dbinit.py,v 1.24 2002-07-26 08:27:00 richard Exp $
import os
def open(name=None):
''' as from the roundupdb method openDB
-
'''
from roundup.hyperdb import String, Password, Date, Link, Multilink
username=String(), password=Password(),
address=String(), realname=String(),
phone=String(), organisation=String(),
- alternate_addresses=String())
+ alternate_addresses=String(),
+ queries=Multilink('query'), roles=String())
user.setkey("username")
# FileClass automatically gets these properties:
platform=Multilink("platform"), version=String(),
targetversion=String(), supportcall=Multilink("support"))
+ #
+ # SECURITY SETTINGS
+ #
+ # new permissions for this schema
+ for cl in 'issue', 'support', 'file', 'msg':
+ db.security.addPermission(name="Edit", klass=cl,
+ description="User is allowed to edit "+cl)
+ db.security.addPermission(name="View", klass=cl,
+ description="User is allowed to access "+cl)
+
+ # Assign the appropriate permissions to the anonymous user's Anonymous
+ # Role. Choices here are:
+ # - Allow anonymous users to register through the web
+ p = db.security.getPermission('Web Registration')
+ db.security.addPermissionToRole('Anonymous', p)
+ # - Allow anonymous (new) users to register through the email gateway
+ p = db.security.getPermission('Email Registration')
+ db.security.addPermissionToRole('Anonymous', p)
+ # - Allow anonymous users access to the "issue" class of data
+ # Note: this also grants access to related information like files,
+ # messages, statuses etc that are linked to issues
+ #p = db.security.getPermission('View', 'issue')
+ #db.security.addPermissionToRole('Anonymous', p)
+ # - Allow anonymous users access to edit the "issue" class of data
+ # Note: this also grants access to create related information like
+ # files and messages etc that are linked to issues
+ #p = db.security.getPermission('Edit', 'issue')
+ #db.security.addPermissionToRole('Anonymous', p)
+
+ # Assign the access and edit permissions for issue, file and message
+ # to regular users now
+ for cl in 'issue', 'support', 'file', 'msg':
+ p = db.security.getPermission('View', cl)
+ db.security.addPermissionToRole('User', p)
+ p = db.security.getPermission('Edit', cl)
+ db.security.addPermissionToRole('User', p)
+
import detectors
detectors.init(db)
user = db.getclass('user')
user.create(username="admin", password=adminpw,
- address=instance_config.ADMIN_EMAIL)
+ address=instance_config.ADMIN_EMAIL, roles="Admin")
+ user.create(username="anonymous", roles='Anonymous')
db.commit()
#
# $Log: not supported by cvs2svn $
+# Revision 1.23 2002/07/14 02:05:54 richard
+# . all storage-specific code (ie. backend) is now implemented by the backends
+#
# Revision 1.22 2002/07/09 03:02:53 richard
# More indexer work:
# - all String properties may now be indexed too. Currently there's a bit of
index 76bd4e5547112cf3b0c2d39797833e1a996ce631..c5a4b8a49e433aec78036fc1db5db08ffd1ced62 100644 (file)
-<!-- $Id: user.item,v 1.2 2002-02-15 07:08:45 richard Exp $-->
+<!-- $Id: user.item,v 1.3 2002-07-26 08:27:00 richard Exp $-->
<table border=0 cellspacing=0 cellpadding=2>
<tr class="strong-header">
<td width=1% nowrap align=right><span class="form-label">Login Password</span></td>
<td class="form-text"><display call="field('password', size=10)"></td>
</tr>
+<require permission="Web Roles">
+ <tr bgcolor="ffffea">
+ <td width=1% nowrap align=right><span class="form-label">Roles</span></td>
+ <td class="form-text"><display call="field('roles', size=10)"></td>
+ </tr>
+</require>
<tr bgcolor="ffffea">
<td width=1% nowrap align=right><span class="form-label">Phone</span></td>
<td class="form-text"><display call="field('phone', size=40)"></td>
diff --git a/roundup/templates/extended/instance_config.py b/roundup/templates/extended/instance_config.py
index ec6a3bf8f2fcaa7cc403849e9098782ae99b533a..532e1494defc2580cb1f322c6b89b2c029cf4fee 100644 (file)
# BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
# SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
#
-# $Id: instance_config.py,v 1.18 2002-05-25 07:16:25 rochecompaan Exp $
+# $Id: instance_config.py,v 1.19 2002-07-26 08:27:00 richard Exp $
MAIL_DOMAIN=MAILHOST=HTTP_HOST=None
HTTP_PORT=0
# Where to place the web filtering HTML on the index page
FILTER_POSITION = 'bottom' # one of 'top', 'bottom', 'top and bottom'
-# Deny or allow anonymous access to the web interface
-ANONYMOUS_ACCESS = 'deny' # either 'deny' or 'allow'
-
-# Deny or allow anonymous users to register through the web interface
-ANONYMOUS_REGISTER = 'deny' # either 'deny' or 'allow'
-
-# Deny or allow anonymous users to register through the mail interface
-ANONYMOUS_REGISTER_MAIL = 'deny' # either 'deny' or 'allow'
+#
+# SECURITY DEFINITIONS
+#
+# define the Roles that a user gets when they register with the tracker
+# these are a comma-separated string of role names (e.g. 'Admin,User')
+NEW_WEB_USER_ROLES = 'User'
+NEW_EMAIL_USER_ROLES = 'User'
# Send nosy messages to the author of the message
MESSAGES_TO_AUTHOR = 'no' # either 'yes' or 'no'
#
# $Log: not supported by cvs2svn $
+# Revision 1.18 2002/05/25 07:16:25 rochecompaan
+# Merged search_indexing-branch with HEAD
+#
# Revision 1.17 2002/05/22 00:32:34 richard
# . changed the default message list in issues to display the message body
# . made backends.__init__ be more specific about which ImportErrors it really
diff --git a/roundup/volatiledb.py b/roundup/volatiledb.py
index 6bffec38ade91ab84575e1d4218cf04b50502cf3..56cf98e4035f4fd9e4d3aedee4a1ddd4be4ddc31 100644 (file)
--- a/roundup/volatiledb.py
+++ b/roundup/volatiledb.py
def index(self, nodeid):
pass
+ def stringFind(self, **requirements):
+ """Locate a particular node by matching a set of its String
+ properties in a caseless search.
+
+ If the property is not a String property, a TypeError is raised.
+
+ The return is a list of the id of all nodes that match.
+ """
+ for propname in requirements.keys():
+ prop = self.properties[propname]
+ if isinstance(not prop, String):
+ raise TypeError, "'%s' not a String property"%propname
+ requirements[propname] = requirements[propname].lower()
+ l = []
+ for nodeid, node in self.store.items():
+ for key, value in requirements.items():
+ if node[key] and node[key].lower() != value:
+ break
+ else:
+ l.append(nodeid)
+ return l
+
+ def getkey(self):
+ """Return the name of the key property for this class or None."""
+ return self.key
+
+ def labelprop(self, default_to_id=0):
+ ''' Return the property name for a label for the given node.
+
+ This method attempts to generate a consistent label for the node.
+ It tries the following in order:
+ 1. key property
+ 2. "name" property
+ 3. "title" property
+ 4. first property from the sorted property name list
+ '''
+ k = self.getkey()
+ if k:
+ return k
+ props = self.getprops()
+ if props.has_key('name'):
+ return 'name'
+ elif props.has_key('title'):
+ return 'title'
+ if default_to_id:
+ return 'id'
+ props = props.keys()
+ props.sort()
+ return props[0]
+
diff --git a/test/test_db.py b/test/test_db.py
index ffc07c7c8e8414c96d7179d750f0171213ab1d3f..d1796720c7835bb7e03420610ce4f26f5b3d3876 100644 (file)
--- a/test/test_db.py
+++ b/test/test_db.py
# BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
# SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
#
-# $Id: test_db.py,v 1.37 2002-07-25 07:14:06 richard Exp $
+# $Id: test_db.py,v 1.38 2002-07-26 08:27:00 richard Exp $
import unittest, os, shutil, time
status = module.Class(db, "status", name=String())
status.setkey("name")
user = module.Class(db, "user", username=String(), password=Password(),
- assignable=Boolean(), age=Number(), roles=Multilink('role'))
+ assignable=Boolean(), age=Number(), roles=String())
user.setkey("username")
file = module.FileClass(db, "file", name=String(), type=String(),
comment=String(indexme="yes"))
#
# $Log: not supported by cvs2svn $
+# Revision 1.37 2002/07/25 07:14:06 richard
+# Bugger it. Here's the current shape of the new security implementation.
+# Still to do:
+# . call the security funcs from cgi and mailgw
+# . change shipped templates to include correct initialisation and remove
+# the old config vars
+# ... that seems like a lot. The bulk of the work has been done though. Honest :)
+#
# Revision 1.36 2002/07/19 03:36:34 richard
# Implemented the destroy() method needed by the session database (and possibly
# others). At the same time, I removed the leading underscores from the hyperdb
index 8b155fc2422845b87a5487696153426b03e54106..6ed63d97c8c6440013f8e44f80214a58ffb6994f 100644 (file)
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
#
-# $Id: test_htmltemplate.py,v 1.18 2002-07-25 07:14:06 richard Exp $
+# $Id: test_htmltemplate.py,v 1.19 2002-07-26 08:27:00 richard Exp $
import unittest, cgi, time, os, shutil
tf.props = ['title']
# admin user
- r = str(self.db.role.lookup('Admin'))
- self.db.user.create(username="admin", roles=[r])
- r = str(self.db.role.lookup('User'))
- self.db.user.create(username="anonymous", roles=[r])
+ self.db.user.create(username="admin", roles='Admin')
+ self.db.user.create(username="anonymous", roles='User')
def testBasic(self):
self.assertEqual(self.tf.execute_template('hello'), 'hello')
tf.nodeid = self.db.issue.create(title="spam", status='1')
# admin user
- r = str(self.db.role.lookup('Admin'))
- self.db.user.create(username="admin", roles=[r])
- r = str(self.db.role.lookup('User'))
- self.db.user.create(username="anonymous", roles=[r])
+ self.db.user.create(username="admin", roles='Admin')
+ self.db.user.create(username="anonymous", roles='User')
def testBasic(self):
self.assertEqual(self.tf.execute_template('hello'), 'hello')
#
# $Log: not supported by cvs2svn $
+# Revision 1.18 2002/07/25 07:14:06 richard
+# Bugger it. Here's the current shape of the new security implementation.
+# Still to do:
+# . call the security funcs from cgi and mailgw
+# . change shipped templates to include correct initialisation and remove
+# the old config vars
+# ... that seems like a lot. The bulk of the work has been done though. Honest :)
+#
# Revision 1.17 2002/07/18 23:07:07 richard
# Unit tests and a few fixes.
#
diff --git a/test/test_init.py b/test/test_init.py
index 1a9520ddf9b28a4e9ab64c06e24ebe417026d509..11e0aeb58413588bb5b75524ce1a316c3af494a0 100644 (file)
--- a/test/test_init.py
+++ b/test/test_init.py
# BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
# SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
#
-# $Id: test_init.py,v 1.13 2002-07-14 02:05:54 richard Exp $
+# $Id: test_init.py,v 1.14 2002-07-26 08:27:00 richard Exp $
import unittest, os, shutil, errno, imp, sys
l = db.keyword.list()
ae(l, [])
l = db.user.list()
- ae(l, ['1'])
+ ae(l, ['1', '2'])
l = db.msg.list()
ae(l, [])
l = db.file.list()
#
# $Log: not supported by cvs2svn $
+# Revision 1.13 2002/07/14 02:05:54 richard
+# . all storage-specific code (ie. backend) is now implemented by the backends
+#
# Revision 1.12 2002/07/11 01:13:13 richard
# *** empty log message ***
#
diff --git a/test/test_mailgw.py b/test/test_mailgw.py
index e8325bc5a89fdd071b412cb05ef3e1d4e5ecd39b..524f85034749a41cffd2a4e5b94658eb387d72a3 100644 (file)
--- a/test/test_mailgw.py
+++ b/test/test_mailgw.py
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
#
-# $Id: test_mailgw.py,v 1.23 2002-07-14 02:02:43 richard Exp $
+# $Id: test_mailgw.py,v 1.24 2002-07-26 08:27:00 richard Exp $
import unittest, cStringIO, tempfile, os, shutil, errno, imp, sys, difflib
#except ImportError :
# import rfc822 as email
-from roundup.mailgw import MailGW
+from roundup.mailgw import MailGW, Unauthorized
from roundup import init, instance
# TODO: make this output only enough equal lines for context, not all of
self.assertEqual('no error', error)
l = self.db.issue.get(nodeid, 'nosy')
l.sort()
- self.assertEqual(l, ['2', '3'])
+ self.assertEqual(l, ['3', '4'])
def testNewIssue(self):
self.doNewIssue()
self.assertEqual('no error', error)
l = self.db.issue.get(nodeid, 'nosy')
l.sort()
- self.assertEqual(l, ['2', '3'])
+ self.assertEqual(l, ['3', '4'])
def testAlternateAddress(self):
message = cStringIO.StringIO('''Content-Type: text/plain;
handler.main(message)
l = self.db.issue.get('1', 'nosy')
l.sort()
- self.assertEqual(l, ['2', '3', '4', '5'])
+ self.assertEqual(l, ['3', '4', '5', '6'])
self.compareStrings(open(os.environ['SENDMAILDEBUG']).read(),
'''FROM: roundup-admin@your.tracker.email.domain.example
handler.main(message)
l = self.db.issue.get('1', 'nosy')
l.sort()
- self.assertEqual(l, ['2'])
+ self.assertEqual(l, ['3'])
# NO NOSY MESSAGE SHOULD BE SENT!
self.assert_(not os.path.exists(os.environ['SENDMAILDEBUG']))
+ def testNewUserAuthor(self):
+ # first without the permission
+ Anonid = self.db.role.lookup('Anonymous')
+ self.db.role.set(Anonid, permissions=[])
+ anonid = self.db.user.lookup('anonymous')
+ self.db.user.set(anonid, roles='Anonymous')
+
+ self.db.security.hasPermission('Email Registration', anonid)
+ l = self.db.user.list()
+ l.sort()
+ s = '''Content-Type: text/plain;
+ charset="iso-8859-1"
+From: fubar <fubar@bork.bork.bork>
+To: issue_tracker@your.tracker.email.domain.example
+Message-Id: <dummy_test_message_id>
+Subject: [issue] Testing...
+
+This is a test submission of a new issue.
+'''
+ message = cStringIO.StringIO(s)
+ handler = self.instance.MailGW(self.instance, self.db)
+ handler.trapExceptions = 0
+ self.assertRaises(Unauthorized, handler.main, message)
+ m = self.db.user.list()
+ m.sort()
+ self.assertEqual(l, m)
+
+ # now with the permission
+ p = self.db.security.getPermission('Email Registration')
+ self.db.role.set(Anonid, permissions=[p])
+ handler = self.instance.MailGW(self.instance, self.db)
+ handler.trapExceptions = 0
+ message = cStringIO.StringIO(s)
+ handler.main(message)
+ m = self.db.user.list()
+ m.sort()
+ self.assertNotEqual(l, m)
+
def testEnc01(self):
self.doNewIssue()
message = cStringIO.StringIO('''Content-Type: text/plain;
#
# $Log: not supported by cvs2svn $
+# Revision 1.23 2002/07/14 02:02:43 richard
+# Fixed the unit tests for the new multilist controls in the mailgw
+#
# Revision 1.22 2002/07/09 01:21:24 richard
# Added ability for unit tests to turn off exception handling in mailgw so
# that exceptions are reported earlier (and hence make sense).
diff --git a/test/test_security.py b/test/test_security.py
index 89b4c461531fe78070e70ef75e4e64ac091ce612..dce68fdb0d8ea0459f2cbc7ae44baa8471095eab 100644 (file)
--- a/test/test_security.py
+++ b/test/test_security.py
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
-# $Id: test_security.py,v 1.1 2002-07-25 07:14:06 richard Exp $
+# $Id: test_security.py,v 1.2 2002-07-26 08:27:00 richard Exp $
import os, unittest, shutil
ei = self.db.security.addPermission(name="Edit", klass="issue",
description="User is allowed to edit issues")
self.db.security.addPermissionToRole('User', ei)
- ai = self.db.security.addPermission(name="Assign", klass="issue",
- description="User may be assigned to issues")
+ ai = self.db.security.addPermission(name="View", klass="issue",
+ description="User is allowed to access issues")
self.db.security.addPermissionToRole('User', ai)
+ def testGetPermission(self):
+ self.db.security.getPermission('Edit')
+ self.db.security.getPermission('View')
+ self.assertRaises(ValueError, self.db.security.getPermission, 'x')
+ self.assertRaises(ValueError, self.db.security.getPermission, 'Edit',
+ 'fubar')
+ ei = self.db.security.addPermission(name="Edit", klass="issue",
+ description="User is allowed to edit issues")
+ self.db.security.getPermission('Edit', 'issue')
+ ai = self.db.security.addPermission(name="View", klass="issue",
+ description="User is allowed to access issues")
+ self.db.security.getPermission('View', 'issue')
+
def testDBinit(self):
- r = str(self.db.role.lookup('Admin'))
- self.db.user.create(username="admin", roles=[r])
- r = str(self.db.role.lookup('User'))
- self.db.user.create(username="anonymous", roles=[r])
+ self.db.user.create(username="admin", roles='Admin')
+ self.db.user.create(username="anonymous", roles='User')
- def testAccess(self):
+ def testAccessControls(self):
self.testDBinit()
self.testInitialiseSecurity()
# test class-level access
userid = self.db.user.lookup('admin')
- self.assertEquals(self.db.security.hasClassPermission('issue',
- 'Edit', userid), 1)
- self.assertEquals(self.db.security.hasClassPermission('user',
- 'Edit', userid), 1)
+ self.assertEquals(self.db.security.hasPermission('Edit', userid,
+ 'issue'), 1)
+ self.assertEquals(self.db.security.hasPermission('Edit', userid,
+ 'user'), 1)
userid = self.db.user.lookup('anonymous')
- self.assertEquals(self.db.security.hasClassPermission('issue',
- 'Edit', userid), 1)
- self.assertEquals(self.db.security.hasClassPermission('user',
- 'Edit', userid), 0)
+ self.assertEquals(self.db.security.hasPermission('Edit', userid,
+ 'issue'), 1)
+ self.assertEquals(self.db.security.hasPermission('Edit', userid,
+ 'user'), 0)
# test node-level access
issueid = self.db.issue.create(title='foo', assignedto='admin')
#
# $Log: not supported by cvs2svn $
+# Revision 1.1 2002/07/25 07:14:06 richard
+# Bugger it. Here's the current shape of the new security implementation.
+# Still to do:
+# . call the security funcs from cgi and mailgw
+# . change shipped templates to include correct initialisation and remove
+# the old config vars
+# ... that seems like a lot. The bulk of the work has been done though. Honest :)
+#
# Revision 1.1 2002/07/10 06:40:01 richard
# ehem, forgot to add
#