~justin-fathomdb/nova/justinsb-openstack-api-volumes

« back to all changes in this revision

Viewing changes to vendor/Twisted-10.0.0/twisted/web/_auth/wrapper.py

  • Committer: Jesse Andrews
  • Date: 2010-05-28 06:05:26 UTC
  • Revision ID: git-v1:bf6e6e718cdc7488e2da87b21e258ccc065fe499
initial commit

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# -*- test-case-name: twisted.web.test.test_httpauth -*-
 
2
# Copyright (c) 2008-2009 Twisted Matrix Laboratories.
 
3
# See LICENSE for details.
 
4
 
 
5
"""
 
6
A guard implementation which supports HTTP header-based authentication
 
7
schemes.
 
8
 
 
9
If no I{Authorization} header is supplied, an anonymous login will be
 
10
attempted by using a L{Anonymous} credentials object.  If such a header is
 
11
supplied and does not contain allowed credentials, or if anonymous login is
 
12
denied, a 401 will be sent in the response along with I{WWW-Authenticate}
 
13
headers for each of the allowed authentication schemes.
 
14
"""
 
15
 
 
16
from zope.interface import implements
 
17
 
 
18
from twisted.python import log
 
19
from twisted.python.components import proxyForInterface
 
20
from twisted.web.resource import IResource, ErrorPage
 
21
from twisted.web import util
 
22
from twisted.cred import error
 
23
from twisted.cred.credentials import Anonymous
 
24
 
 
25
 
 
26
class UnauthorizedResource(object):
 
27
    """
 
28
    Simple IResource to escape Resource dispatch
 
29
    """
 
30
    implements(IResource)
 
31
    isLeaf = True
 
32
 
 
33
 
 
34
    def __init__(self, factories):
 
35
        self._credentialFactories = factories
 
36
 
 
37
 
 
38
    def render(self, request):
 
39
        """
 
40
        Send www-authenticate headers to the client
 
41
        """
 
42
        def generateWWWAuthenticate(scheme, challenge):
 
43
            l = []
 
44
            for k,v in challenge.iteritems():
 
45
                l.append("%s=%s" % (k, quoteString(v)))
 
46
            return "%s %s" % (scheme, ", ".join(l))
 
47
 
 
48
        def quoteString(s):
 
49
            return '"%s"' % (s.replace('\\', '\\\\').replace('"', '\\"'),)
 
50
 
 
51
        request.setResponseCode(401)
 
52
        for fact in self._credentialFactories:
 
53
            challenge = fact.getChallenge(request)
 
54
            request.responseHeaders.addRawHeader(
 
55
                'www-authenticate',
 
56
                generateWWWAuthenticate(fact.scheme, challenge))
 
57
        return 'Unauthorized'
 
58
 
 
59
 
 
60
    def getChildWithDefault(self, path, request):
 
61
        """
 
62
        Disable resource dispatch
 
63
        """
 
64
        return self
 
65
 
 
66
 
 
67
 
 
68
class HTTPAuthSessionWrapper(object):
 
69
    """
 
70
    Wrap a portal, enforcing supported header-based authentication schemes.
 
71
 
 
72
    @ivar _portal: The L{Portal} which will be used to retrieve L{IResource}
 
73
        avatars.
 
74
 
 
75
    @ivar _credentialFactories: A list of L{ICredentialFactory} providers which
 
76
        will be used to decode I{Authorization} headers into L{ICredentials}
 
77
        providers.
 
78
    """
 
79
    implements(IResource)
 
80
    isLeaf = False
 
81
 
 
82
    def __init__(self, portal, credentialFactories):
 
83
        """
 
84
        Initialize a session wrapper
 
85
 
 
86
        @type portal: C{Portal}
 
87
        @param portal: The portal that will authenticate the remote client
 
88
 
 
89
        @type credentialFactories: C{Iterable}
 
90
        @param credentialFactories: The portal that will authenticate the
 
91
            remote client based on one submitted C{ICredentialFactory}
 
92
        """
 
93
        self._portal = portal
 
94
        self._credentialFactories = credentialFactories
 
95
 
 
96
 
 
97
    def _authorizedResource(self, request):
 
98
        """
 
99
        Get the L{IResource} which the given request is authorized to receive.
 
100
        If the proper authorization headers are present, the resource will be
 
101
        requested from the portal.  If not, an anonymous login attempt will be
 
102
        made.
 
103
        """
 
104
        authheader = request.getHeader('authorization')
 
105
        if not authheader:
 
106
            return util.DeferredResource(self._login(Anonymous()))
 
107
 
 
108
        factory, respString = self._selectParseHeader(authheader)
 
109
        if factory is None:
 
110
            return UnauthorizedResource(self._credentialFactories)
 
111
        try:
 
112
            credentials = factory.decode(respString, request)
 
113
        except error.LoginFailed:
 
114
            return UnauthorizedResource(self._credentialFactories)
 
115
        except:
 
116
            log.err(None, "Unexpected failure from credentials factory")
 
117
            return ErrorPage(500, None, None)
 
118
        else:
 
119
            return util.DeferredResource(self._login(credentials))
 
120
 
 
121
 
 
122
    def render(self, request):
 
123
        """
 
124
        Find the L{IResource} avatar suitable for the given request, if
 
125
        possible, and render it.  Otherwise, perhaps render an error page
 
126
        requiring authorization or describing an internal server failure.
 
127
        """
 
128
        return self._authorizedResource(request).render(request)
 
129
 
 
130
 
 
131
    def getChildWithDefault(self, path, request):
 
132
        """
 
133
        Inspect the Authorization HTTP header, and return a deferred which,
 
134
        when fired after successful authentication, will return an authorized
 
135
        C{Avatar}. On authentication failure, an C{UnauthorizedResource} will
 
136
        be returned, essentially halting further dispatch on the wrapped
 
137
        resource and all children
 
138
        """
 
139
        # Don't consume any segments of the request - this class should be
 
140
        # transparent!
 
141
        request.postpath.insert(0, request.prepath.pop())
 
142
        return self._authorizedResource(request)
 
143
 
 
144
 
 
145
    def _login(self, credentials):
 
146
        """
 
147
        Get the L{IResource} avatar for the given credentials.
 
148
 
 
149
        @return: A L{Deferred} which will be called back with an L{IResource}
 
150
            avatar or which will errback if authentication fails.
 
151
        """
 
152
        d = self._portal.login(credentials, None, IResource)
 
153
        d.addCallbacks(self._loginSucceeded, self._loginFailed)
 
154
        return d
 
155
 
 
156
 
 
157
    def _loginSucceeded(self, (interface, avatar, logout)):
 
158
        """
 
159
        Handle login success by wrapping the resulting L{IResource} avatar
 
160
        so that the C{logout} callback will be invoked when rendering is
 
161
        complete.
 
162
        """
 
163
        class ResourceWrapper(proxyForInterface(IResource, 'resource')):
 
164
            """
 
165
            Wrap an L{IResource} so that whenever it or a child of it
 
166
            completes rendering, the cred logout hook will be invoked.
 
167
 
 
168
            An assumption is made here that exactly one L{IResource} from
 
169
            among C{avatar} and all of its children will be rendered.  If
 
170
            more than one is rendered, C{logout} will be invoked multiple
 
171
            times and probably earlier than desired.
 
172
            """
 
173
            def getChildWithDefault(self, name, request):
 
174
                """
 
175
                Pass through the lookup to the wrapped resource, wrapping
 
176
                the result in L{ResourceWrapper} to ensure C{logout} is
 
177
                called when rendering of the child is complete.
 
178
                """
 
179
                return ResourceWrapper(self.resource.getChildWithDefault(name, request))
 
180
 
 
181
            def render(self, request):
 
182
                """
 
183
                Hook into response generation so that when rendering has
 
184
                finished completely, C{logout} is called.
 
185
                """
 
186
                request.notifyFinish().addCallback(lambda ign: logout())
 
187
                return super(ResourceWrapper, self).render(request)
 
188
 
 
189
        return ResourceWrapper(avatar)
 
190
 
 
191
 
 
192
    def _loginFailed(self, result):
 
193
        """
 
194
        Handle login failure by presenting either another challenge (for
 
195
        expected authentication/authorization-related failures) or a server
 
196
        error page (for anything else).
 
197
        """
 
198
        if result.check(error.Unauthorized, error.LoginFailed):
 
199
            return UnauthorizedResource(self._credentialFactories)
 
200
        else:
 
201
            log.err(
 
202
                result,
 
203
                "HTTPAuthSessionWrapper.getChildWithDefault encountered "
 
204
                "unexpected error")
 
205
            return ErrorPage(500, None, None)
 
206
 
 
207
 
 
208
    def _selectParseHeader(self, header):
 
209
        """
 
210
        Choose an C{ICredentialFactory} from C{_credentialFactories}
 
211
        suitable to use to decode the given I{Authenticate} header.
 
212
 
 
213
        @return: A two-tuple of a factory and the remaining portion of the
 
214
            header value to be decoded or a two-tuple of C{None} if no
 
215
            factory can decode the header value.
 
216
        """
 
217
        elements = header.split(' ')
 
218
        scheme = elements[0].lower()
 
219
        for fact in self._credentialFactories:
 
220
            if fact.scheme == scheme:
 
221
                return (fact, ' '.join(elements[1:]))
 
222
        return (None, None)