File: wsgi_adaptors.py

package info (click to toggle)
python-authkit 0.4.1~r143-1
  • links: PTS, VCS
  • area: main
  • in suites: lenny
  • size: 740 kB
  • ctags: 703
  • sloc: python: 4,643; makefile: 39; sh: 33
file content (200 lines) | stat: -rw-r--r-- 6,761 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
"""Authorization objects for checking permissions

In the AuthKit model permissions are handled by ``Permission`` objects.
Authorization objects are used to check permissions and to raise
``NotAuthenticatedError`` or ``NotAuthorizedError`` if there is no user or the
user is not authorized. The execeptions are converted to HTTP responses which
are then intercepted and handled by the authentication middleware.

The way permissions objects should be checked depends on where abouts in the
application stack the check occurs and so different authorization objects exist
to make checks at different parts of the stack. You can of course create 
your own permission objects to be authorized by the middleware and decorator
defined here. See the permissions docs or the AuthKit manual for more 
information.

Framework implementors might also create their own implementations of AuthKit
authorization objects. For example the ``authkit.pylons_adaptors`` module
contains some Pylons-specific authorization objects which you'll want to use
if you are using AuthKit with Pylons.

For an example of how to use permission objects have a look at the
``AuthorizeExampleApp`` class in the ``authorize.py`` example in the ``examples``
directory or have a look at the AuthKit manual.
"""

from paste import httpexceptions

class PermissionSetupError(Exception):
    pass

#
# Errors
#

class PermissionError(httpexceptions.HTTPClientError):
    """
    Base class from which ``NotAuthenticatedError`` and ``NotAuthorizedError`` 
    are inherited.
    """
    pass

class NotAuthenticatedError(PermissionError):
    """
    Raised when a permission check fails because the user is not authenticated.

    The exception is caught by the ``httpexceptions`` middleware and converted into
    a ``401`` HTTP response which is intercepted by the authentication middleware
    triggering a sign in.
    """
    required_headers = ()
    code = 401
    title = 'Not Authenticated'

class NotAuthorizedError(PermissionError):
    """
    Raised when a permission check fails because the user is not authorized.

    The exception is caught by the ``httpexceptions`` middleware and converted into
    a ``403`` HTTP response which is intercepted by the authentication middleware
    triggering a sign in.
    """
    code = 403
    title = 'Forbidden'
    explanation = ('Access was denied to this resource.')

class NonConformingPermissionError(Exception):
    """
    Raised when a custom permission object is not behaving in a compliant way
    """
    pass

class _PermissionStartResponse(object):
    def __init__(self, status, headers, exc_info=None):
        pass

class _PermissionList(list):
    def __iter__(self):
        raise FiddledWith('Fiddled with response')
    
class _FiddledWith(Exception):
    pass



#
# Authorize Objects
#

class _Authorize(object):
    def __init__(self, app, permission):
        self.app = app
        self.permission = permission

    def __call__(self, environ, start_response):
        if not environ.has_key('authkit.authenticate'):
            raise Exception(
                "Authenticate middleware not present"
            ) 
        # Could also check that status and response haven't changed here?
        try:
            return self.permission.check(self.app, environ, start_response)
        except NotAuthenticatedError:
            if environ.has_key('REMOTE_USER'):
                raise NonConformingPermissionError(
                    'Faulty permission: NotAuthenticatedError raised '
                    'but REMOTE_USER key is present.'
                )
            else:
                raise

class _PermissionStartResponse(object):
    def __init__(self, status, headers, exc_info=None):
        pass
        
def middleware(app, permission):
    """
    Returns an WSGI app wrapped in authorization middleware and on each request
    will check the permission specified.

    Takes the arguments:

    ``app``
        The WSGI application to be wrapped

    ``permission``
        The AuthKit permission object to be checked.

    The ``httpexceptions`` and ``authkit.authenticate.middleware`` middleware need to
    be wrap this middleware otherwise any errors triggered will not be intercepted.

    See the AuthKit manual for an example.
    """
    return _Authorize(app, permission)

def authorize(permission):
    """
    This is an authorize decorator (requires Python 2.4) which can be used
    to decorate a function. It takes the permission to check as its only 
    argument.

    See the AuthKit manual for an example.
    """
    def decorate(func):
        def input(self, environ, start_response):
            def app(environ, start_response):
                return func(self, environ, start_response)
            return permission.check(app, environ, start_response)
        return input
    return decorate

def authorize_request(environ, permission):
    """
    This function can be used within a controller action to ensure that no code 
    after the function call is executed if the user doesn't pass the permission
    check specified by ``permission``.

    .. Note ::

        Unlike the ``authorize()`` decorator or
        ``authkit.authorize.middleware`` middleware, this function has no
        access to the WSGI response so cannot be used to check response-based
        permissions.  Since almost all AuthKit permissions are request-based
        this shouldn't be a big problem unless you are defining your own 
        advanced permission checks.
    """
    error = PermissionSetupError(
        'The permissions being authorized require access to a response '
        'and so cannot be used to authorize based on a request alone. '
        'Try using the authkit.authorize.middleware or the authorize decorator.'
    )
    try:
        def dummy_app(environ, start_response):
            if not start_response == _PermissionStartResponse:
                raise _FiddledWith('Fiddled with start_response %r'%start_response)
            start_response(
                '1000 Test Response For Permissions Check', 
                [('Content-type','text/plain')]
            )
            return _PermissionList('''Dummy response from permission check.''')
        
        if not isinstance(
            permission.check(
                dummy_app, 
                environ, 
                _PermissionStartResponse
            ), 
            _PermissionList
        ):
            raise _FiddledWith('Fiddled with response')
    except _FiddledWith:
        raise error

def authorized(environ, permission):
    try:
        authorize_request(environ, permission)
    except (NotAuthorizedError, NotAuthenticatedError):
        return False
    else:
        return True