22Security Mechanisms
33===================
44
5- :Version: $Revision: 1.11 $
5+ :Version: $Revision: 1.12 $
66
77Current situation
88=================
4141 than the From address. Support for strong identification through digital
4242 signatures should be added.
43435. The command-line tool has no logical controls.
44+ 6. The anonymous control needs revising - there should only be one way to be
45+ an anonymous user, not two (currently there is user==None and
46+ user=='anonymous).
4447
4548
4649Possible approaches
@@ -138,8 +141,8 @@ with specific nodes by way of their user-linked properties.
138141
139142A security module defines::
140143
141- class InMemoryImmutableClass (hyperdb.Class):
142- ''' Don't allow changes to this class's nodes.
144+ class InMemoryClass (hyperdb.Class):
145+ ''' Just be an in-memory class
143146 '''
144147 def __init__(self, db, classname, **properties):
145148 ''' Set up an in-memory store for the nodes of this class
@@ -154,20 +157,21 @@ A security module defines::
154157 '''
155158
156159 def set(self, *args):
157- raise ValueError, "%s are immutable"%self.__class__.__name__
160+ ''' Set values on the node
161+ '''
158162
159- class PermissionClass(InMemoryImmutableClass ):
163+ class PermissionClass(InMemoryClass ):
160164 ''' Include the default attributes:
161165 - name (String)
162- - classname (String)
166+ - klass (String)
163167 - description (String)
164168
165- The classname may be unset, indicating that this permission is not
169+ The klass may be unset, indicating that this permission is not
166170 locked to a particular class. That means there may be multiple
167171 Permissions for the same name for different classes.
168172 '''
169173
170- class RoleClass(InMemoryImmutableClass ):
174+ class RoleClass(InMemoryClass ):
171175 ''' Include the default attributes:
172176 - name (String, key)
173177 - description (String)
@@ -179,51 +183,23 @@ A security module defines::
179183 ''' Initialise the permission and role classes, and add in the
180184 base roles (for admin user).
181185 '''
182- # use a weak ref to avoid circularity
183- self.db = weakref.proxy(db)
184-
185- # create the permission class instance (we only need one))
186- self.permission = PermissionClass(db, "permission")
187-
188- # create the role class instance (we only need one)
189- self.role = RoleClass(db, "role")
190-
191- # the default Roles
192- self.addRole(name="User", description="A regular user, no privs")
193- self.addRole(name="Admin", description="An admin user, full privs")
194- self.addRole(name="No Rego",
195- description="A user who can't register")
196-
197- ee = self.addPermission(name="Edit",
198- description="User may edit everthing")
199- self.addPermissionToRole('Admin', ee)
200- ae = self.addPermission(name="Assign",
201- description="User may be assigned to anything")
202- self.addPermissionToRole('Admin', ae)
203-
204- # initialise the permissions and roles needed for the UIs
205- from roundup import cgi_client, mailgw
206- cgi_client.initialiseSecurity(self)
207- mailgw.initialiseSecurity(self)
208186
209187 def hasClassPermission(self, db, classname, permission, userid):
210188 ''' Look through all the Roles, and hence Permissions, and see if
211189 "permission" is there for the specified classname.
212190
213191 '''
214192
215- def hasNodePermission(self, db, classname, nodeid, userid, properties ):
193+ def hasNodePermission(self, db, classname, nodeid, **propspec ):
216194 ''' Check the named properties of the given node to see if the
217195 userid appears in them. If it does, then the user is granted
218196 this permission check.
219197
220- 'propspec' consists of a list of property names. The property
221- names must be the name of a property of classname, or a
222- KeyError is raised. That property must be a Link or Multilink
223- property, or a TypeError is raised.
198+ 'propspec' consists of a set of properties and values that
199+ must be present on the given node for access to be granted.
224200
225- If the property is a Link, the userid must match the property
226- value. If the property is a Multilink, the userid must appear
201+ If a property is a Link, the value must match the property
202+ value. If a property is a Multilink, the value must appear
227203 in the Multilink list.
228204 '''
229205
@@ -251,13 +227,9 @@ permissions like so (this example is ``cgi_client.py``)::
251227 This function is directly invoked by security.Security.__init__()
252228 as a part of the Security object instantiation.
253229 '''
254- newid = security.addPermission(name="Web Access",
255- description="User may use the web interface")
256- security.addToRole('User', newid)
257- security.addToRole('No Rego', newid)
258230 newid = security.addPermission(name="Web Registration",
259- description="User may register through the web")
260- security.addToRole('User ', newid)
231+ description="Anonymous users may register through the web")
232+ security.addToRole('Anonymous ', newid)
261233
262234The instance dbinit module then has in ``open()``::
263235
@@ -266,10 +238,10 @@ The instance dbinit module then has in ``open()``::
266238 db = Database(instance_config, name)
267239
268240 # add some extra permissions and associate them with roles
269- ei = db.security.addPermission(name="Edit", classname ="issue",
241+ ei = db.security.addPermission(name="Edit", klass ="issue",
270242 description="User is allowed to edit issues")
271243 db.security.addPermissionToRole('User', ei)
272- ai = db.security.addPermission(name="Assign", classname ="issue",
244+ ai = db.security.addPermission(name="Assign", klass ="issue",
273245 description="User may be assigned to issues")
274246 db.security.addPermissionToRole('User', ei)
275247
@@ -284,29 +256,65 @@ In the dbinit ``init()``::
284256 r = db.getclass('role').lookup('User')
285257 user.create(username="anonymous", roles=[r])
286258
287- Then in the code that matters, calls to ``hasPermission`` are made to
288- determine if the user has permission to perform some action::
259+ Then in the code that matters, calls to ``hasClassPermission`` and
260+ ``hasNodePermission`` are made to determine if the user has permission
261+ to perform some action::
289262
290- if db.security.hasClassPermission('issue', 'Edit', self.user ):
263+ if db.security.hasClassPermission('issue', 'Edit', userid ):
291264 # all ok
292265
293- if db.security.hasNodePermission('issue', nodeid, self.user,
294- ['assignedto']):
266+ if db.security.hasNodePermission('issue', nodeid, assignedto=userid):
295267 # all ok
296268
297- The htmltemplate will implement a new tag, <permission> which has the form::
269+ Code in the core will make use of these methods, as should code in auditors in
270+ custom templates. The htmltemplate will implement a new tag, ``<require>``
271+ which has the form::
298272
299- <permission require= name,name,name node= assignedto>
273+ <require permission=" name,name,name" assignedto="$userid" status="open" >
300274 HTML to display if the user has the permission.
301275 <else>
302276 HTML to display if the user does not have the permission.
303- </permission>
277+ </require>
278+
279+ where:
280+
281+ - the permission attribute gives a comma-separated list of permission names.
282+ These are checked in turn using ``hasClassPermission`` and requires one to
283+ be OK.
284+ - the other attributes are lookups on the node using ``hasNodePermission``. If
285+ the attribute value is "$userid" then the current user's userid is tested.
286+
287+ Any of these tests must pass or the ``<require>`` check will fail. The section
288+ of html within the side of the ``<else>`` that fails is remove from processing.
289+
290+ Implementation as shipped
291+ -------------------------
292+
293+ A set of Permissions are built in to the security module by default:
304294
305- where the require attribute gives a comma-separated list of permission names
306- which are required, and the node attribute gives a comma-separated list of
307- node properties whose value must match the current user's id. Either of these
308- tests must pass or the permission check will fail. The section of html within
309- the side of the ``<else>`` that fails is remove from processing.
295+ - Edit (everything)
296+ - Access (everything)
297+ - Assign (everything)
298+
299+ The default interfaces define:
300+
301+ - Web Registration
302+ - Email Registration
303+
304+ These are hooked into the default Roles:
305+
306+ - Admin (Edit everything, Access everything, Assign everything)
307+ - User ()
308+ - Anonymous (Web Registration, Email Registration)
309+
310+ And finally, the "admin" user gets the "Admin" Role, and the "anonymous" user
311+ gets the "Anonymous" assigned when the database is initialised on installation.
312+ The two default schemas then define:
313+
314+ - Edit issue, Access issue (both)
315+ - Edit support, Access support (extended only)
316+
317+ and assign those Permissions to the "User" Role.
310318
311319
312320Authentication of Users
@@ -322,6 +330,14 @@ first message into the tracker should be at a lower level of trust to those
322330who supply their signature to an admin for submission to their user details.
323331
324332
333+ Anonymous Users
334+ ---------------
335+
336+ The "anonymous" user must always exist, and defines the access permissions for
337+ anonymous users. The three ANONYMOUS_ configuration variables are subsumed by
338+ this new functionality.
339+
340+
325341Action
326342======
327343
0 commit comments