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

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

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

行番号 
1# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
2# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
3##########################################################################
4#
5# Copyright (c) 2005 Imaginary Landscape LLC and Contributors.
6#
7# Permission is hereby granted, free of charge, to any person obtaining
8# a copy of this software and associated documentation files (the
9# "Software"), to deal in the Software without restriction, including
10# without limitation the rights to use, copy, modify, merge, publish,
11# distribute, sublicense, and/or sell copies of the Software, and to
12# permit persons to whom the Software is furnished to do so, subject to
13# the following conditions:
14#
15# The above copyright notice and this permission notice shall be
16# included in all copies or substantial portions of the Software.
17#
18# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
22# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
23# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
24# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25##########################################################################
26"""
27Implementation of cookie signing as done in `mod_auth_tkt
28<http://www.openfusion.com.au/labs/mod_auth_tkt/>`_.
29
30mod_auth_tkt is an Apache module that looks for these signed cookies
31and sets ``REMOTE_USER``, ``REMOTE_USER_TOKENS`` (a comma-separated
32list of groups) and ``REMOTE_USER_DATA`` (arbitrary string data).
33
34This module is an alternative to the ``paste.auth.cookie`` module;
35it's primary benefit is compatibility with mod_auth_tkt, which in turn
36makes it possible to use the same authentication process with
37non-Python code run under Apache.
38"""
39
40import time as time_mod
41import md5
42import Cookie
43from paste import request
44
45class AuthTicket(object):
46
47    """
48    This class represents an authentication token.  You must pass in
49    the shared secret, the userid, and the IP address.  Optionally you
50    can include tokens (a list of strings, representing role names),
51    'user_data', which is arbitrary data available for your own use in
52    later scripts.  Lastly, you can override the cookie name and
53    timestamp.
54
55    Once you provide all the arguments, use .cookie_value() to
56    generate the appropriate authentication ticket.  .cookie()
57    generates a Cookie object, the str() of which is the complete
58    cookie header to be sent.
59
60    CGI usage::
61
62        token = auth_tkt.AuthTick('sharedsecret', 'username',
63            os.environ['REMOTE_ADDR'], tokens=['admin'])
64        print 'Status: 200 OK'
65        print 'Content-type: text/html'
66        print token.cookie()
67        print
68        ... redirect HTML ...
69
70    Webware usage::
71
72        token = auth_tkt.AuthTick('sharedsecret', 'username',
73            self.request().environ()['REMOTE_ADDR'], tokens=['admin'])
74        self.response().setCookie('auth_tkt', token.cookie_value())
75
76    Be careful not to do an HTTP redirect after login; use meta
77    refresh or Javascript -- some browsers have bugs where cookies
78    aren't saved when set on a redirect.
79    """
80
81    def __init__(self, secret, userid, ip, tokens=(), user_data='',
82                 time=None, cookie_name='auth_tkt',
83                 secure=False):
84        self.secret = secret
85        self.userid = userid
86        self.ip = ip
87        self.tokens = ','.join(tokens)
88        self.user_data = user_data
89        if time is None:
90            self.time = time_mod.time()
91        else:
92            self.time = time
93        self.cookie_name = cookie_name
94        self.secure = secure
95
96    def digest(self):
97        return calculate_digest(
98            self.ip, self.time, self.secret, self.userid, self.tokens,
99            self.user_data)
100
101    def cookie_value(self):
102        v = '%s%08x%s!' % (self.digest(), int(self.time), self.userid)
103        if self.tokens:
104            v += self.tokens + '!'
105        v += self.user_data
106        return v
107
108    def cookie(self):
109        c = Cookie.SimpleCookie()
110        c[self.cookie_name] = self.cookie_value().encode('base64').strip().replace('\n', '')
111        c[self.cookie_name]['path'] = '/'
112        if self.secure:
113            c[self.cookie_name]['secure'] = 'true'
114        return c
115
116class BadTicket(Exception):
117    """
118    Exception raised when a ticket can't be parsed.  If we get
119    far enough to determine what the expected digest should have
120    been, expected is set.  This should not be shown by default,
121    but can be useful for debugging.
122    """
123    def __init__(self, msg, expected=None):
124        self.expected = expected
125        Exception.__init__(self, msg)
126
127def parse_ticket(secret, ticket, ip):
128    """
129    Parse the ticket, returning (timestamp, userid, tokens, user_data).
130
131    If the ticket cannot be parsed, ``BadTicket`` will be raised with
132    an explanation.
133    """
134    ticket = ticket.strip('"')
135    digest = ticket[:32]
136    try:
137        timestamp = int(ticket[32:40], 16)
138    except ValueError, e:
139        raise BadTicket('Timestamp is not a hex integer: %s' % e)
140    try:
141        userid, data = ticket[40:].split('!', 1)
142    except ValueError:
143        raise BadTicket('userid is not followed by !')
144    if '!' in data:
145        tokens, user_data = data.split('!', 1)
146    else:
147        # @@: Is this the right order?
148        tokens = ''
149        user_data = data
150   
151    expected = calculate_digest(ip, timestamp, secret,
152                                userid, tokens, user_data)
153
154    if expected != digest:
155        raise BadTicket('Digest signature is not correct',
156                        expected=(expected, digest))
157
158    tokens = tokens.split(',')
159
160    return (timestamp, userid, tokens, user_data)
161   
162def calculate_digest(ip, timestamp, secret, userid, tokens, user_data):
163    secret = maybe_encode(secret)
164    userid = maybe_encode(userid)
165    tokens = maybe_encode(tokens)
166    user_data = maybe_encode(user_data)
167    digest0 = md5.new(
168        encode_ip_timestamp(ip, timestamp) + secret + userid + '\0'
169        + tokens + '\0' + user_data).hexdigest()
170    digest = md5.new(digest0 + secret).hexdigest()
171    return digest
172
173def encode_ip_timestamp(ip, timestamp):
174    ip_chars = ''.join(map(chr, map(int, ip.split('.'))))
175    t = int(timestamp)
176    ts = ((t & 0xff000000) >> 24,
177          (t & 0xff0000) >> 16,
178          (t & 0xff00) >> 8,
179          t & 0xff)
180    ts_chars = ''.join(map(chr, ts))
181    return ip_chars + ts_chars
182
183def maybe_encode(s, encoding='utf8'):
184    if isinstance(s, unicode):
185        s = s.encode(encoding)
186    return s
187
188class AuthTKTMiddleware(object):
189
190    """
191    Middleware that checks for signed cookies that match what
192    `mod_auth_tkt <http://www.openfusion.com.au/labs/mod_auth_tkt/>`_
193    looks for (if you have mod_auth_tkt installed, you don't need this
194    middleware, since Apache will set the environmental variables for
195    you).
196
197    Arguments:
198   
199    ``secret``:
200        A secret that should be shared by any instances of this application.
201        If this app is served from more than one machine, they should all
202        have the same secret.
203       
204    ``cookie_name``:
205        The name of the cookie to read and write from.  Default ``auth_tkt``.
206       
207    ``secure``:
208        If the cookie should be set as 'secure' (only sent over SSL) and if
209        the login must be over SSL.
210       
211    ``include_ip``:
212        If the cookie should include the user's IP address.  If so, then
213        if they change IPs their cookie will be invalid.
214       
215    ``logout_path``:
216        The path under this middleware that should signify a logout.  The
217        page will be shown as usual, but the user will also be logged out
218        when they visit this page.
219       
220    If used with mod_auth_tkt, then these settings (except logout_path) should
221    match the analogous Apache configuration settings.
222
223    This also adds two functions to the request:
224
225    ``environ['paste.auth_tkt.set_user'](userid, tokens='', user_data='')``
226
227        This sets a cookie that logs the user in.  ``tokens`` is a
228        string (comma-separated groups) or a list of strings.
229        ``user_data`` is a string for your own use.
230
231    ``environ['paste.auth_tkt.logout_user']()``
232
233        Logs out the user.
234    """
235
236    def __init__(self, app, secret, cookie_name='auth_tkt', secure=False,
237                 include_ip=True, logout_path=None):
238        self.app = app
239        self.secret = secret
240        self.cookie_name = cookie_name
241        self.secure = secure
242        self.include_ip = include_ip
243        self.logout_path = logout_path
244
245    def __call__(self, environ, start_response):
246        cookies = request.get_cookies(environ)
247        if cookies.has_key(self.cookie_name):
248            cookie_value = cookies[self.cookie_name].value
249        else:
250            cookie_value = ''
251        if cookie_value:
252            if self.include_ip:
253                remote_addr = environ['REMOTE_ADDR']
254            else:
255                # mod_auth_tkt uses this dummy value when IP is not
256                # checked:
257                remote_addr = '0.0.0.0'
258            # @@: This should handle bad signatures better:
259            # Also, timeouts should cause cookie refresh
260            timestamp, userid, tokens, user_data = parse_ticket(
261                self.secret, cookie_value, remote_addr)
262            tokens = ','.join(tokens)
263            environ['REMOTE_USER'] = userid
264            if environ.get('REMOTE_USER_TOKENS'):
265                # We want to add tokens/roles to what's there:
266                tokens = environ['REMOTE_USER_TOKENS'] + ',' + tokens
267            environ['REMOTE_USER_TOKENS'] = tokens
268            environ['REMOTE_USER_DATA'] = user_data
269            environ['AUTH_TYPE'] = 'cookie'
270        set_cookies = []
271        def set_user(userid, tokens='', user_data=''):
272            set_cookies.extend(self.set_user_cookie(
273                environ, userid, tokens, user_data))
274        def logout_user():
275            set_cookies.extend(self.logout_user_cookie(environ))
276        environ['paste.auth_tkt.set_user'] = set_user
277        environ['paste.auth_tkt.logout_user'] = logout_user
278        if self.logout_path and environ.get('PATH_INFO') == self.logout_path:
279            logout_user()
280        def cookie_setting_start_response(status, headers, exc_info=None):
281            headers.extend(set_cookies)
282            return start_response(status, headers, exc_info)
283        return self.app(environ, cookie_setting_start_response)
284
285    def set_user_cookie(self, environ, userid, tokens, user_data):
286        if not isinstance(tokens, basestring):
287            tokens = ','.join(tokens)
288        if self.include_ip:
289            remote_addr = environ['REMOTE_ADDR']
290        else:
291            remote_addr = '0.0.0.0'
292        ticket = AuthTicket(
293            self.secret,
294            userid,
295            remote_addr,
296            tokens=tokens,
297            user_data=user_data,
298            cookie_name=self.cookie_name,
299            secure=self.secure)
300        # @@: Should we set REMOTE_USER etc in the current
301        # environment right now as well?
302        cookies = [
303            ('Set-Cookie', '%s=%s; Path=/' % (
304            self.cookie_name, ticket.cookie_value()))]
305        return cookies
306   
307    def logout_user_cookie(self, environ):
308        cur_domain = environ.get('HTTP_HOST', environ.get('SERVER_NAME'))
309        wild_domain = '.' + cur_domain
310        cookies = [
311            ('Set-Cookie', '%s=""; Path=/' % self.cookie_name),
312            ('Set-Cookie', '%s=""; Path=/; Domain=%s' %
313             (self.cookie_name, cur_domain)),
314            ('Set-Cookie', '%s=""; Path=/; Domain=%s' %
315             (self.cookie_name, wild_domain)),
316            ]
317        return cookies
318
319def make_auth_tkt_middleware(
320    app,
321    global_conf,
322    secret=None,
323    cookie_name='auth_tkt',
324    secure=False,
325    include_ip=True,
326    logout_path=None):
327    """
328    Creates the `AuthTKTMiddleware
329    <class-paste.auth.auth_tkt.AuthTKTMiddleware.html>`_.
330
331    ``secret`` is requird, but can be set globally or locally.
332    """
333    from paste.deploy.converters import asbool
334    secure = asbool(secure)
335    include_ip = asbool(include_ip)
336    if secret is None:
337        secret = global_conf.get('secret')
338    if not secret:
339        raise ValueError(
340            "You must provide a 'secret' (in global or local configuration)")
341    return AuthTKTMiddleware(
342        app, secret, cookie_name, secure, include_ip, logout_path or None)
Note: リポジトリブラウザについてのヘルプは TracBrowser を参照してください。