3dfa8bd442c13ec068ca90589a10e55fc9f5b9fa
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 # default permissions - Admin may do anything
107 for p in 'create edit retire view'.split():
108 p = self.addPermission(name=p.title(),
109 description="User may %s everthing"%p)
110 self.addPermissionToRole('Admin', p)
112 # initialise the permissions and roles needed for the UIs
113 from roundup.cgi import client
114 client.initialiseSecurity(self)
115 from roundup import mailgw
116 mailgw.initialiseSecurity(self)
118 def getPermission(self, permission, classname=None, properties=None,
119 check=None):
120 ''' Find the Permission matching the name and for the class, if the
121 classname is specified.
123 Raise ValueError if there is no exact match.
124 '''
125 if not self.permission.has_key(permission):
126 raise ValueError, 'No permission "%s" defined'%permission
128 if classname:
129 try:
130 self.db.getclass(classname)
131 except KeyError:
132 raise ValueError, 'No class "%s" defined'%classname
134 # look through all the permissions of the given name
135 tester = Permission(permission, klass=classname, properties=properties,
136 check=check)
137 for perm in self.permission[permission]:
138 if perm == tester:
139 return perm
140 raise ValueError, 'No permission "%s" defined for "%s"'%(permission,
141 classname)
143 def hasPermission(self, permission, userid, classname=None,
144 property=None, itemid=None):
145 '''Look through all the Roles, and hence Permissions, and
146 see if "permission" exists given the constraints of
147 classname, property and itemid.
149 If classname is specified (and only classname) then the
150 search will match if there is *any* Permission for that
151 classname, even if the Permission has additional
152 constraints.
154 If property is specified, the Permission matched must have
155 either no properties listed or the property must appear in
156 the list.
158 If itemid is specified, the Permission matched must have
159 either no check function defined or the check function,
160 when invoked, must return a True value.
162 Note that this functionality is actually implemented by the
163 Permission.test() method.
164 '''
165 if itemid and classname is None:
166 raise ValueError, 'classname must accompany itemid'
167 for rolename in self.db.user.get_roles(userid):
168 if not rolename or not self.role.has_key(rolename):
169 continue
170 # for each of the user's Roles, check the permissions
171 for perm in self.role[rolename].permissions:
172 # permission match?
173 if perm.test(self.db, permission, classname, property,
174 userid, itemid):
175 return 1
176 return 0
178 def addPermission(self, **propspec):
179 ''' Create a new Permission with the properties defined in
180 'propspec'. See the Permission class for the possible
181 keyword args.
182 '''
183 perm = Permission(**propspec)
184 self.permission.setdefault(perm.name, []).append(perm)
185 return perm
187 def addRole(self, **propspec):
188 ''' Create a new Role with the properties defined in 'propspec'
189 '''
190 role = Role(**propspec)
191 self.role[role.name] = role
192 return role
194 def addPermissionToRole(self, rolename, permission, classname=None,
195 properties=None, check=None):
196 ''' Add the permission to the role's permission list.
198 'rolename' is the name of the role to add the permission to.
200 'permission' is either a Permission *or* a permission name
201 accompanied by 'classname' (thus in the second case a Permission
202 is obtained by passing 'permission' and 'classname' to
203 self.getPermission)
204 '''
205 if not isinstance(permission, Permission):
206 permission = self.getPermission(permission, classname,
207 properties, check)
208 role = self.role[rolename.lower()]
209 role.permissions.append(permission)
211 # vim: set filetype=python sts=4 sw=4 et si :