Code

3dfa8bd442c13ec068ca90589a10e55fc9f5b9fa
[roundup.git] / roundup / security.py
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 :