1 """Handle the security declarations used in Roundup trackers.
2 """
3 __docformat__ = 'restructuredtext'
5 import weakref
7 from roundup import hyperdb, support
9 class Permission:
10 ''' Defines a Permission with the attributes
11 - name
12 - description
13 - klass (optional)
14 - properties (optional)
15 - check function (optional)
17 The klass may be unset, indicating that this permission is not
18 locked to a particular class. That means there may be multiple
19 Permissions for the same name for different classes.
21 If property names are set, permission is restricted to those
22 properties only.
24 If check function is set, permission is granted only when
25 the function returns value interpreted as boolean true.
26 The function is called with arguments db, userid, itemid.
27 '''
28 def __init__(self, name='', description='', klass=None,
29 properties=None, check=None):
30 self.name = name
31 self.description = description
32 self.klass = klass
33 self.properties = properties
34 self._properties_dict = support.TruthDict(properties)
35 self.check = check
37 def test(self, db, permission, classname, property, userid, itemid):
38 if permission != self.name:
39 return 0
41 # are we checking the correct class
42 if self.klass is not None and self.klass != classname:
43 return 0
45 # what about property?
46 if property is not None and not self._properties_dict[property]:
47 return 0
49 # check code
50 if itemid is not None and self.check is not None:
51 if not self.check(db, userid, itemid):
52 return 0
54 # we have a winner
55 return 1
57 def __repr__(self):
58 return '<Permission 0x%x %r,%r,%r,%r>'%(id(self), self.name,
59 self.klass, self.properties, self.check)
61 def __cmp__(self, other):
62 if self.name != other.name:
63 return cmp(self.name, other.name)
65 if self.klass != other.klass: return 1
66 if self.properties != other.properties: return 1
67 if self.check != other.check: return 1
69 # match
70 return 0
72 class Role:
73 ''' Defines a Role with the attributes
74 - name
75 - description
76 - permissions
77 '''
78 def __init__(self, name='', description='', permissions=None):
79 self.name = name.lower()
80 self.description = description
81 if permissions is None:
82 permissions = []
83 self.permissions = permissions
85 def __repr__(self):
86 return '<Role 0x%x %r,%r>'%(id(self), self.name, self.permissions)
88 class Security:
89 def __init__(self, db):
90 ''' Initialise the permission and role classes, and add in the
91 base roles (for admin user).
92 '''
93 self.db = weakref.proxy(db) # use a weak ref to avoid circularity
95 # permssions are mapped by name to a list of Permissions by class
96 self.permission = {}
98 # roles are mapped by name to the Role
99 self.role = {}
101 # the default Roles
102 self.addRole(name="User", description="A regular user, no privs")
103 self.addRole(name="Admin", description="An admin user, full privs")
104 self.addRole(name="Anonymous", description="An anonymous user")
106 ce = self.addPermission(name="Create",
107 description="User may create everthing")
108 self.addPermissionToRole('Admin', ce)
109 ee = self.addPermission(name="Edit",
110 description="User may edit everthing")
111 self.addPermissionToRole('Admin', ee)
112 ae = self.addPermission(name="View",
113 description="User may access everything")
114 self.addPermissionToRole('Admin', ae)
116 # initialise the permissions and roles needed for the UIs
117 from roundup.cgi import client
118 client.initialiseSecurity(self)
119 from roundup import mailgw
120 mailgw.initialiseSecurity(self)
122 def getPermission(self, permission, classname=None, properties=None,
123 check=None):
124 ''' Find the Permission matching the name and for the class, if the
125 classname is specified.
127 Raise ValueError if there is no exact match.
128 '''
129 if not self.permission.has_key(permission):
130 raise ValueError, 'No permission "%s" defined'%permission
132 if classname:
133 try:
134 self.db.getclass(classname)
135 except KeyError:
136 raise ValueError, 'No class "%s" defined'%classname
138 # look through all the permissions of the given name
139 tester = Permission(permission, klass=classname, properties=properties,
140 check=check)
141 for perm in self.permission[permission]:
142 if perm == tester:
143 return perm
144 raise ValueError, 'No permission "%s" defined for "%s"'%(permission,
145 classname)
147 def hasPermission(self, permission, userid, classname=None,
148 property=None, itemid=None):
149 '''Look through all the Roles, and hence Permissions, and
150 see if "permission" exists given the constraints of
151 classname, property and itemid.
153 If classname is specified (and only classname) then the
154 search will match if there is *any* Permission for that
155 classname, even if the Permission has additional
156 constraints.
158 If property is specified, the Permission matched must have
159 either no properties listed or the property must appear in
160 the list.
162 If itemid is specified, the Permission matched must have
163 either no check function defined or the check function,
164 when invoked, must return a True value.
166 Note that this functionality is actually implemented by the
167 Permission.test() method.
168 '''
169 roles = self.db.user.get(userid, 'roles')
170 if roles is None:
171 return 0
172 if itemid and classname is None:
173 raise ValueError, 'classname must accompany itemid'
174 for rolename in [x.lower().strip() for x in roles.split(',')]:
175 if not rolename or not self.role.has_key(rolename):
176 continue
177 # for each of the user's Roles, check the permissions
178 for perm in self.role[rolename].permissions:
179 # permission match?
180 if perm.test(self.db, permission, classname, property,
181 userid, itemid):
182 return 1
183 return 0
185 def addPermission(self, **propspec):
186 ''' Create a new Permission with the properties defined in
187 'propspec'. See the Permission class for the possible
188 keyword args.
189 '''
190 perm = Permission(**propspec)
191 self.permission.setdefault(perm.name, []).append(perm)
192 return perm
194 def addRole(self, **propspec):
195 ''' Create a new Role with the properties defined in 'propspec'
196 '''
197 role = Role(**propspec)
198 self.role[role.name] = role
199 return role
201 def addPermissionToRole(self, rolename, permission, classname=None,
202 properties=None, check=None):
203 ''' Add the permission to the role's permission list.
205 'rolename' is the name of the role to add the permission to.
207 'permission' is either a Permission *or* a permission name
208 accompanied by 'classname' (thus in the second case a Permission
209 is obtained by passing 'permission' and 'classname' to
210 self.getPermission)
211 '''
212 if not isinstance(permission, Permission):
213 permission = self.getPermission(permission, classname,
214 properties, check)
215 role = self.role[rolename.lower()]
216 role.permissions.append(permission)
218 # vim: set filetype=python sts=4 sw=4 et si :