Code

Enhance and simplify logging.
[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         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 :