root/AuthKit/trunk/authkit/permissions.py

Revision 100 (checked in by thejimmyg, 1 year ago)

Fixed #18 reversing not working and removed some tab characters

Line 
1 """Permission objects
2
3 Permission objects are used to define which users should have access to a particular
4 resource. They are checked using some of the authorization objects either in the
5 ``authkit.authorize`` module or ``authkit.pylons_adaptors`` module if you are using
6 Pylons.
7
8 Permissions objects are very similar to WSGI applications and can perform a
9 check based on the request or the response. Not all of the authorization
10 objects have access to the response because the permission might be checked as
11 part of a code block before the response is generated. This leads to two
12 classes of permissions, request-based (which can be checked anywhere) and
13 responce-based which can only be checked when the authorization object has
14 access to the response.
15
16 All the built-in AuthKit permissions are request-based but you can use the
17 permissions objects defined in this module or create your own derived from
18 ``authkit.permission.Permission``.
19
20 Permissions are described in detail in the AuthKit manual.
21 """
22
23 from authkit.authorize import PermissionError, NotAuthenticatedError
24 from authkit.authorize import NotAuthorizedError, middleware
25
26 import datetime
27 import logging
28 log = logging.getLogger('authkit.permissions')
29
30 class AuthKitConfigError(Exception):
31     """
32     Raised when there is a problem with the
33     configuration options chosen for the authenticate middleware
34     """
35     pass
36    
37 no_authkit_users_in_environ = AuthKitConfigError(
38     'No authkit.users object exists in the environment. You may have '
39     'forgotton to specify a Users object or are using the the default '
40     'valid_password() method in the authenticate middleware when you '
41     'may have meant to specify your own.'
42 )
43
44 #
45 # Permission Classes
46 #
47
48 class Permission(object):
49     """
50     The base class for all permissions objects.
51
52     The ``check()`` method is called by the authorization object to check the
53     permission. Permissions should return the original status, headers and
54     response or raise a ``NotAuthorizedError`` when their ``check()`` method is
55     called.
56
57     .. Note ::
58    
59         The WSGI ``app`` can only be called once by the ``check()`` method.
60         This means that you cannot write permisisons objects that perform
61         logical ``not`` and ``or`` operations on other permissions objects
62         since doing so might require the same app to be called multiple times.
63         A permission object to perform an ``and`` operation is feasible and has
64         been impleneted as the ``And`` permission class. 
65        
66    """
67
68     def check(self, app, environ, start_response):
69         return app(environ, start_response)
70
71 class RequestPermission(Permission):
72     """
73     The base class for all request-based permissions
74     """
75     pass
76
77 class _TestBadlyLabelledResponseBasedPermission(RequestPermission):
78     def check(self, app, environ, start_response):
79         def start_response(a,b,c=None):
80             return start_response(a,b,c)
81         return app(environ, start_response)
82        
83 class UserIn(RequestPermission):
84     """
85     Checks the ``REMOTE_USER`` is one of the users specified.
86    
87     Takes the following arguments:
88
89     ``users``
90         A list of usernames which are valid
91
92     If there is no ``REMOTE_USER`` a ``NotAuthenticatedError`` is raised. If
93     the ``REMOTE_USER`` is not in ``users`` a ``NotAuthorizedError`` is raised.
94
95     Usernames supplied to ``users`` are treated case insensitively.
96     """
97
98     def __init__(self, users):
99         if isinstance(users, list) or isinstance(users, tuple):
100             users_ = []
101             for user in users:
102                 users_.append(user.lower())
103             self.users = users_
104         elif isinstance(users, str):
105             self.users = [users]
106         else:
107             raise PermissionSetupError('Expected users to be a list or a string, not %r'%users)
108      
109     def check(self, app, environ, start_response):
110         if 'REMOTE_USER' not in environ:
111             raise NotAuthenticatedError('Not Authenticated')
112         if environ['REMOTE_USER'] not in self.users:
113             raise NotAuthorizedError('You are not one of the users allowed to access this resource.')
114         return app(environ, start_response)
115
116 class Exists(RequestPermission):
117     """
118     Checks the specified key is present in the ``environ``.
119    
120     Takes the following arguments:
121
122     ``key``
123         The required key
124
125     ``error``
126         The error to be raised if the key is missing. XXX This argument may be deprecated soon.
127
128     """
129
130     def __init__(self, key, error=NotAuthorizedError('Not Authorized')):
131         self.key = key
132         self.error = error
133    
134     def check(self, app, environ, start_response):
135         if self.key not in environ:
136             raise self.error
137         return app(environ, start_response)
138        
139 class And(RequestPermission):
140     """
141     Checks all the permission objects listed as keyword arguments in turn.
142     Permissions are checked from left to right. The error raised by the ``And``
143     permission is the error raised by the first permission check to fail.
144     """
145
146     def __init__(self, *permissions):
147         if len(permissions) < 2:
148             raise PermissionSetupError('Expected at least 2 permissions objects')
149         permissions = list(permissions)
150         permissions.reverse()
151         self.permissions = permissions
152        
153     def check(self, app, environ, start_response):
154         for permission in self.permissions:
155             app = middleware(app, permission)
156         #raise Exception(app, self.permissions)
157         return app(environ, start_response)
158
159 class RemoteUser(RequestPermission):
160     """
161     Checks someone is signed in by checking for the presence of the
162     ``REMOTE_USER``.
163    
164     If ``accept_empty`` is ``False`` (the default) then an empty ``REMOTE_USER``
165     will not be accepted and the value of ``REMOTE_USER`` must evaluate to
166     ``True`` in Python.
167     """
168
169     def __init__(self, accept_empty=False):
170         self.accept_empty = accept_empty
171
172     def check(self, app, environ, start_response):
173         if 'REMOTE_USER' not in environ:
174             raise NotAuthenticatedError('Not Authenticated')
175         elif self.accept_empty==False and not environ['REMOTE_USER']:
176             raise NotAuthorizedError('Not Authorized')
177         return app(environ, start_response)
178
179 #
180 # Permissions to work with the AuthKit user management API
181 #
182
183 class HasAuthKitRole(RequestPermission):
184     """
185     Designed to work with the user management API described in the AuthKit manual.
186
187     This permission checks that the signed in user has any if the roles specified
188     in ``roles``. If ``all`` is ``True``, the user must have all the roles for
189     the permission check to pass.
190     """
191
192     def __init__(self, roles, all=False, error=None):
193         if isinstance(roles, str):
194             roles = [roles]
195         self.all = all
196         self.roles = roles
197         self.error = error
198        
199     def check(self, app, environ, start_response):
200         """
201         Should return True if the user has the role or
202         False if the user doesn't exist or doesn't have the role.
203
204         In this implementation role names are case insensitive.
205         """
206        
207         if not environ.get('authkit.users'):
208             raise no_authkit_users_in_environ
209         if not environ.get('REMOTE_USER'):
210             if self.error:
211                 raise self.error
212             raise NotAuthenticatedError('Not authenticated')
213        
214         users = environ['authkit.users']
215         if not users.user_exists(environ['REMOTE_USER']):
216             raise NotAuthorizedError('No such user')
217         # Check the groups specified when setup actually exist
218         for role in self.roles:
219             if not users.role_exists(role):
220                 raise Exception("No such role %r exists"%role)
221         if self.all:
222             for role in self.roles:
223                 if not users.user_has_role(environ['REMOTE_USER'], role):
224                     if self.error:
225                         raise self.error
226                     else:
227                         raise NotAuthorizedError(
228                             "User doesn't have the role %s"%role.lower()
229                         )
230             return app(environ, start_response)
231         else:
232             for role in self.roles:
233                 if users.user_has_role(environ['REMOTE_USER'], role):
234                     return app(environ, start_response)
235             if self.error:
236                 raise self.error
237             else:
238                 raise NotAuthorizedError(
239                     "User doesn't have any of the specified roles"
240                 )
241    
242 class HasAuthKitGroup(RequestPermission):
243     """
244     Designed to work with the user management API described in the AuthKit manual.
245
246     This permission checks that the signed in user is in one of the groups specified
247     in ``groups``.
248     """
249
250     def __init__(self, groups, error=None):
251         if isinstance(groups, str):
252             groups = [groups]
253         self.groups = groups
254         self.error = error
255        
256     def check(self, app, environ, start_response):
257         """
258         Should return True if the user has the group or
259         False if the user doesn't exist or doesn't have the group.
260
261         In this implementation group names are case insensitive.
262         """
263         if not environ.get('authkit.users'):
264             raise no_authkit_users_in_environ
265         if not environ.get('REMOTE_USER'):
266             if self.error:
267                 raise self.error
268             raise NotAuthenticatedError('Not authenticated')
269         users = environ['authkit.users']
270         # Check the groups specified when setup actually exist
271         for group in self.groups:
272             if group is not None:
273                 if not users.group_exists(group):
274                     raise Exception("No such group %r exists"%group)
275        
276         if not users.user_exists(environ['REMOTE_USER']):
277             raise NotAuthorizedError('No such user')
278         for group in self.groups:
279             if users.user_has_group(environ['REMOTE_USER'], group):
280                 return app(environ, start_response)
281         if self.error:
282             raise self.error
283         else:
284             raise NotAuthorizedError(
285                 "User is not a member of the specified group(s) %r"%self.groups
286             )
287
288 class ValidAuthKitUser(UserIn):
289     """
290     Checks that the signed in user is one of the users specified when setting up
291     the user management API.
292     """
293     def __init__(self):
294         pass
295    
296     def check(self, app, environ, start_response):
297         if 'authkit.users' not in environ:
298             raise no_authkit_users_in_environ
299         if not environ.get('REMOTE_USER'):
300             raise NotAuthenticatedError('Not Authenticated')
301         if not environ['authkit.users'].user_exists(environ['REMOTE_USER']):
302             raise NotAuthorizedError(
303                 'You are not one of the users allowed to access this resource.'
304             )
305         return app(environ, start_response)
306
307 class FromIP(RequestPermission):
308     """
309     Checks that the remote host specified in the environment ``key`` is one
310     of the hosts specified in ``hosts``.
311     """
312     def __init__(self, hosts, key='REMOTE_ADDR'):
313         self.hosts = hosts
314         if not isinstance(self.hosts, (list, tuple)):
315             self.hosts = [hosts]
316         self.key = key
317        
318     def check(self, app, environ, start_response):
319         if self.key not in environ:
320             raise Exception(
321                 "No such key %r in environ so cannot check the host"%self.key
322             )
323         if not environ.get(self.key) in self.hosts:
324             raise NotAuthorizedError('Host %r not allowed'%environ.get(self.key))
325         return app(environ, start_response)
326
327 class BetweenTimes(RequestPermission):
328     """
329     Only grants access if the request is made on or after ``start`` and
330     before ``end``. Times should be specified as datetime.time objects.
331     """
332     def __init__(self, start, end):
333         self.start = start
334         self.end = end
335
336     def check(self, app, environ, start_response):
337         today = datetime.datetime.now()
338         now = datetime.time(today.hour, today.minute, today.second, today.microsecond)
339         error = NotAuthorizedError("Not authorized at this time of day")
340         if self.end > self.start:
341             if now >= self.start and now < self.end:
342                 return app(environ, start_response)
343             else:
344                 raise error
345         else:
346             if now < datetime.time(23, 59, 59, 999999) and now >= self.start:
347                 return app(environ, start_response)
348             elif now >= datetime.time(0) and now < self.end:
349                 return app(environ, start_response)
350             else:
351                 raise error
Note: See TracBrowser for help on using the browser.