root/galaxy-central/eggs/Paste-1.6-py2.6.egg/paste/auth/digest.py @ 3

リビジョン 3, 7.9 KB (コミッタ: kohda, 14 年 前)

Install Unix tools  http://hannonlab.cshl.edu/galaxy_unix_tools/galaxy.html

行番号 
1# (c) 2005 Clark C. Evans
2# This module is part of the Python Paste Project and is released under
3# the MIT License: http://www.opensource.org/licenses/mit-license.php
4# This code was written with funding by http://prometheusresearch.com
5"""
6Digest HTTP/1.1 Authentication
7
8This module implements ``Digest`` authentication as described by
9RFC 2617 [1]_ .
10
11Basically, you just put this module before your application, and it
12takes care of requesting and handling authentication requests.  This
13module has been tested with several common browsers "out-in-the-wild".
14
15>>> from paste.wsgilib import dump_environ
16>>> from paste.httpserver import serve
17>>> # from paste.auth.digest import digest_password, AuthDigestHandler
18>>> realm = 'Test Realm'
19>>> def authfunc(environ, realm, username):
20...     return digest_password(realm, username, username)
21>>> serve(AuthDigestHandler(dump_environ, realm, authfunc))
22serving on...
23
24This code has not been audited by a security expert, please use with
25caution (or better yet, report security holes). At this time, this
26implementation does not provide for further challenges, nor does it
27support Authentication-Info header.  It also uses md5, and an option
28to use sha would be a good thing.
29
30.. [1] http://www.faqs.org/rfcs/rfc2617.html
31"""
32from paste.httpexceptions import HTTPUnauthorized
33from paste.httpheaders import *
34import md5, time, random
35
36def digest_password(realm, username, password):
37    """ construct the appropriate hashcode needed for HTTP digest """
38    return md5.md5("%s:%s:%s" % (username, realm, password)).hexdigest()
39
40class AuthDigestAuthenticator(object):
41    """ implementation of RFC 2617 - HTTP Digest Authentication """
42    def __init__(self, realm, authfunc):
43        self.nonce    = {} # list to prevent replay attacks
44        self.authfunc = authfunc
45        self.realm    = realm
46
47    def build_authentication(self, stale = ''):
48        """ builds the authentication error """
49        nonce  = md5.md5(
50            "%s:%s" % (time.time(), random.random())).hexdigest()
51        opaque = md5.md5(
52            "%s:%s" % (time.time(), random.random())).hexdigest()
53        self.nonce[nonce] = None
54        parts = {'realm': self.realm, 'qop': 'auth',
55                 'nonce': nonce, 'opaque': opaque }
56        if stale:
57            parts['stale'] = 'true'
58        head = ", ".join(['%s="%s"' % (k, v) for (k, v) in parts.items()])
59        head = [("WWW-Authenticate", 'Digest %s' % head)]
60        return HTTPUnauthorized(headers=head)
61
62    def compute(self, ha1, username, response, method,
63                      path, nonce, nc, cnonce, qop):
64        """ computes the authentication, raises error if unsuccessful """
65        if not ha1:
66            return self.build_authentication()
67        ha2 = md5.md5('%s:%s' % (method, path)).hexdigest()
68        if qop:
69            chk = "%s:%s:%s:%s:%s:%s" % (ha1, nonce, nc, cnonce, qop, ha2)
70        else:
71            chk = "%s:%s:%s" % (ha1, nonce, ha2)
72        if response != md5.md5(chk).hexdigest():
73            if nonce in self.nonce:
74                del self.nonce[nonce]
75            return self.build_authentication()
76        pnc = self.nonce.get(nonce,'00000000')
77        if nc <= pnc:
78            if nonce in self.nonce:
79                del self.nonce[nonce]
80            return self.build_authentication(stale = True)
81        self.nonce[nonce] = nc
82        return username
83
84    def authenticate(self, environ):
85        """ This function takes a WSGI environment and authenticates
86            the request returning authenticated user or error.
87        """
88        method = REQUEST_METHOD(environ)
89        fullpath = SCRIPT_NAME(environ) + PATH_INFO(environ)
90        authorization = AUTHORIZATION(environ)
91        if not authorization:
92            return self.build_authentication()
93        (authmeth, auth) = authorization.split(" ", 1)
94        if 'digest' != authmeth.lower():
95            return self.build_authentication()
96        amap = {}
97        for itm in auth.split(", "):
98            (k,v) = [s.strip() for s in itm.split("=", 1)]
99            amap[k] = v.replace('"', '')
100        try:
101            username = amap['username']
102            authpath = amap['uri']
103            nonce    = amap['nonce']
104            realm    = amap['realm']
105            response = amap['response']
106            assert authpath.split("?", 1)[0] in fullpath
107            assert realm == self.realm
108            qop      = amap.get('qop', '')
109            cnonce   = amap.get('cnonce', '')
110            nc       = amap.get('nc', '00000000')
111            if qop:
112                assert 'auth' == qop
113                assert nonce and nc
114        except:
115            return self.build_authentication()
116        ha1 = self.authfunc(environ, realm, username)
117        return self.compute(ha1, username, response, method, authpath,
118                            nonce, nc, cnonce, qop)
119
120    __call__ = authenticate
121
122class AuthDigestHandler(object):
123    """
124    middleware for HTTP Digest authentication (RFC 2617)
125
126    This component follows the procedure below:
127
128        0. If the REMOTE_USER environment variable is already populated;
129           then this middleware is a no-op, and the request is passed
130           along to the application.
131
132        1. If the HTTP_AUTHORIZATION header was not provided or specifies
133           an algorithem other than ``digest``, then a HTTPUnauthorized
134           response is generated with the challenge.
135
136        2. If the response is malformed or or if the user's credientials
137           do not pass muster, another HTTPUnauthorized is raised.
138
139        3. If all goes well, and the user's credintials pass; then
140           REMOTE_USER environment variable is filled in and the
141           AUTH_TYPE is listed as 'digest'.
142
143    Parameters:
144
145        ``application``
146
147            The application object is called only upon successful
148            authentication, and can assume ``environ['REMOTE_USER']``
149            is set.  If the ``REMOTE_USER`` is already set, this
150            middleware is simply pass-through.
151
152        ``realm``
153
154            This is a identifier for the authority that is requesting
155            authorization.  It is shown to the user and should be unique
156            within the domain it is being used.
157
158        ``authfunc``
159
160            This is a callback function which performs the actual
161            authentication; the signature of this callback is:
162
163              authfunc(environ, realm, username) -> hashcode
164
165            This module provides a 'digest_password' helper function
166            which can help construct the hashcode; it is recommended
167            that the hashcode is stored in a database, not the user's
168            actual password (since you only need the hashcode).
169    """
170    def __init__(self, application, realm, authfunc):
171        self.authenticate = AuthDigestAuthenticator(realm, authfunc)
172        self.application = application
173
174    def __call__(self, environ, start_response):
175        username = REMOTE_USER(environ)
176        if not username:
177            result = self.authenticate(environ)
178            if isinstance(result, str):
179                AUTH_TYPE.update(environ,'digest')
180                REMOTE_USER.update(environ, result)
181            else:
182                return result.wsgi_application(environ, start_response)
183        return self.application(environ, start_response)
184
185middleware = AuthDigestHandler
186
187__all__ = ['digest_password', 'AuthDigestHandler' ]
188
189def make_digest(app, global_conf, realm, authfunc, **kw):
190    """
191    Grant access via digest authentication
192
193    Config looks like this::
194
195      [filter:grant]
196      use = egg:Paste#auth_digest
197      realm=myrealm
198      authfunc=somepackage.somemodule:somefunction
199     
200    """
201    from paste.util.import_string import eval_import
202    import types
203    authfunc = eval_import(authfunc)
204    assert isinstance(authfunc, types.FunctionType), "authfunc must resolve to a function"
205    return AuthDigestHandler(app, realm, authfunc)
206
207if "__main__" == __name__:
208    import doctest
209    doctest.testmod(optionflags=doctest.ELLIPSIS)
Note: リポジトリブラウザについてのヘルプは TracBrowser を参照してください。