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 roles = self.db.user.get(userid, 'roles')
166 if roles is None:
167 return 0
168 if itemid and classname is None:
169 raise ValueError, 'classname must accompany itemid'
170 for rolename in [x.lower().strip() for x in roles.split(',')]:
171 if not rolename or not self.role.has_key(rolename):
172 continue
173 # for each of the user's Roles, check the permissions
174 for perm in self.role[rolename].permissions:
175 # permission match?
176 if perm.test(self.db, permission, classname, property,
177 userid, itemid):
178 return 1
179 return 0
181 def addPermission(self, **propspec):
182 ''' Create a new Permission with the properties defined in
183 'propspec'. See the Permission class for the possible
184 keyword args.
185 '''
186 perm = Permission(**propspec)
187 self.permission.setdefault(perm.name, []).append(perm)
188 return perm
190 def addRole(self, **propspec):
191 ''' Create a new Role with the properties defined in 'propspec'
192 '''
193 role = Role(**propspec)
194 self.role[role.name] = role
195 return role
197 def addPermissionToRole(self, rolename, permission, classname=None,
198 properties=None, check=None):
199 ''' Add the permission to the role's permission list.
201 'rolename' is the name of the role to add the permission to.
203 'permission' is either a Permission *or* a permission name
204 accompanied by 'classname' (thus in the second case a Permission
205 is obtained by passing 'permission' and 'classname' to
206 self.getPermission)
207 '''
208 if not isinstance(permission, Permission):
209 permission = self.getPermission(permission, classname,
210 properties, check)
211 role = self.role[rolename.lower()]
212 role.permissions.append(permission)
214 # vim: set filetype=python sts=4 sw=4 et si :