source: publico/il.spdo/trunk/Paste-1.7.5.1-py2.6.egg/paste/auth/auth_tkt.py @ 5327

Última Alteração nesse arquivo desde 5327 foi 5327, incluída por fabianosantos, 8 anos atrás

Import inicial.

File size: 14.1 KB
Linha 
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
41try:
42    from hashlib import md5
43except ImportError:
44    from md5 import md5
45import Cookie
46from paste import request
47from urllib import quote as url_quote
48from urllib import unquote as url_unquote
49
50
51class AuthTicket(object):
52
53    """
54    This class represents an authentication token.  You must pass in
55    the shared secret, the userid, and the IP address.  Optionally you
56    can include tokens (a list of strings, representing role names),
57    'user_data', which is arbitrary data available for your own use in
58    later scripts.  Lastly, you can override the cookie name and
59    timestamp.
60
61    Once you provide all the arguments, use .cookie_value() to
62    generate the appropriate authentication ticket.  .cookie()
63    generates a Cookie object, the str() of which is the complete
64    cookie header to be sent.
65
66    CGI usage::
67
68        token = auth_tkt.AuthTick('sharedsecret', 'username',
69            os.environ['REMOTE_ADDR'], tokens=['admin'])
70        print 'Status: 200 OK'
71        print 'Content-type: text/html'
72        print token.cookie()
73        print
74        ... redirect HTML ...
75
76    Webware usage::
77
78        token = auth_tkt.AuthTick('sharedsecret', 'username',
79            self.request().environ()['REMOTE_ADDR'], tokens=['admin'])
80        self.response().setCookie('auth_tkt', token.cookie_value())
81
82    Be careful not to do an HTTP redirect after login; use meta
83    refresh or Javascript -- some browsers have bugs where cookies
84    aren't saved when set on a redirect.
85    """
86
87    def __init__(self, secret, userid, ip, tokens=(), user_data='',
88                 time=None, cookie_name='auth_tkt',
89                 secure=False):
90        self.secret = secret
91        self.userid = userid
92        self.ip = ip
93        self.tokens = ','.join(tokens)
94        self.user_data = user_data
95        if time is None:
96            self.time = time_mod.time()
97        else:
98            self.time = time
99        self.cookie_name = cookie_name
100        self.secure = secure
101
102    def digest(self):
103        return calculate_digest(
104            self.ip, self.time, self.secret, self.userid, self.tokens,
105            self.user_data)
106
107    def cookie_value(self):
108        v = '%s%08x%s!' % (self.digest(), int(self.time), url_quote(self.userid))
109        if self.tokens:
110            v += self.tokens + '!'
111        v += self.user_data
112        return v
113
114    def cookie(self):
115        c = Cookie.SimpleCookie()
116        c[self.cookie_name] = self.cookie_value().encode('base64').strip().replace('\n', '')
117        c[self.cookie_name]['path'] = '/'
118        if self.secure:
119            c[self.cookie_name]['secure'] = 'true'
120        return c
121
122
123class BadTicket(Exception):
124    """
125    Exception raised when a ticket can't be parsed.  If we get
126    far enough to determine what the expected digest should have
127    been, expected is set.  This should not be shown by default,
128    but can be useful for debugging.
129    """
130    def __init__(self, msg, expected=None):
131        self.expected = expected
132        Exception.__init__(self, msg)
133
134
135def parse_ticket(secret, ticket, ip):
136    """
137    Parse the ticket, returning (timestamp, userid, tokens, user_data).
138
139    If the ticket cannot be parsed, ``BadTicket`` will be raised with
140    an explanation.
141    """
142    ticket = ticket.strip('"')
143    digest = ticket[:32]
144    try:
145        timestamp = int(ticket[32:40], 16)
146    except ValueError, e:
147        raise BadTicket('Timestamp is not a hex integer: %s' % e)
148    try:
149        userid, data = ticket[40:].split('!', 1)
150    except ValueError:
151        raise BadTicket('userid is not followed by !')
152    userid = url_unquote(userid)
153    if '!' in data:
154        tokens, user_data = data.split('!', 1)
155    else:
156        # @@: Is this the right order?
157        tokens = ''
158        user_data = data
159
160    expected = calculate_digest(ip, timestamp, secret,
161                                userid, tokens, user_data)
162
163    if expected != digest:
164        raise BadTicket('Digest signature is not correct',
165                        expected=(expected, digest))
166
167    tokens = tokens.split(',')
168
169    return (timestamp, userid, tokens, user_data)
170
171
172def calculate_digest(ip, timestamp, secret, userid, tokens, user_data):
173    secret = maybe_encode(secret)
174    userid = maybe_encode(userid)
175    tokens = maybe_encode(tokens)
176    user_data = maybe_encode(user_data)
177    digest0 = md5(
178        encode_ip_timestamp(ip, timestamp) + secret + userid + '\0'
179        + tokens + '\0' + user_data).hexdigest()
180    digest = md5(digest0 + secret).hexdigest()
181    return digest
182
183
184def encode_ip_timestamp(ip, timestamp):
185    ip_chars = ''.join(map(chr, map(int, ip.split('.'))))
186    t = int(timestamp)
187    ts = ((t & 0xff000000) >> 24,
188          (t & 0xff0000) >> 16,
189          (t & 0xff00) >> 8,
190          t & 0xff)
191    ts_chars = ''.join(map(chr, ts))
192    return ip_chars + ts_chars
193
194
195def maybe_encode(s, encoding='utf8'):
196    if isinstance(s, unicode):
197        s = s.encode(encoding)
198    return s
199
200
201class AuthTKTMiddleware(object):
202
203    """
204    Middleware that checks for signed cookies that match what
205    `mod_auth_tkt <http://www.openfusion.com.au/labs/mod_auth_tkt/>`_
206    looks for (if you have mod_auth_tkt installed, you don't need this
207    middleware, since Apache will set the environmental variables for
208    you).
209
210    Arguments:
211
212    ``secret``:
213        A secret that should be shared by any instances of this application.
214        If this app is served from more than one machine, they should all
215        have the same secret.
216
217    ``cookie_name``:
218        The name of the cookie to read and write from.  Default ``auth_tkt``.
219
220    ``secure``:
221        If the cookie should be set as 'secure' (only sent over SSL) and if
222        the login must be over SSL. (Defaults to False)
223
224    ``httponly``:
225        If the cookie should be marked as HttpOnly, which means that it's
226        not accessible to JavaScript. (Defaults to False)
227
228    ``include_ip``:
229        If the cookie should include the user's IP address.  If so, then
230        if they change IPs their cookie will be invalid.
231
232    ``logout_path``:
233        The path under this middleware that should signify a logout.  The
234        page will be shown as usual, but the user will also be logged out
235        when they visit this page.
236
237    If used with mod_auth_tkt, then these settings (except logout_path) should
238    match the analogous Apache configuration settings.
239
240    This also adds two functions to the request:
241
242    ``environ['paste.auth_tkt.set_user'](userid, tokens='', user_data='')``
243
244        This sets a cookie that logs the user in.  ``tokens`` is a
245        string (comma-separated groups) or a list of strings.
246        ``user_data`` is a string for your own use.
247
248    ``environ['paste.auth_tkt.logout_user']()``
249
250        Logs out the user.
251    """
252
253    def __init__(self, app, secret, cookie_name='auth_tkt', secure=False,
254                 include_ip=True, logout_path=None, httponly=False,
255                 no_domain_cookie=True, current_domain_cookie=True,
256                 wildcard_cookie=True):
257        self.app = app
258        self.secret = secret
259        self.cookie_name = cookie_name
260        self.secure = secure
261        self.httponly = httponly
262        self.include_ip = include_ip
263        self.logout_path = logout_path
264        self.no_domain_cookie = no_domain_cookie
265        self.current_domain_cookie = current_domain_cookie
266        self.wildcard_cookie = wildcard_cookie
267
268    def __call__(self, environ, start_response):
269        cookies = request.get_cookies(environ)
270        if self.cookie_name in cookies:
271            cookie_value = cookies[self.cookie_name].value
272        else:
273            cookie_value = ''
274        if cookie_value:
275            if self.include_ip:
276                remote_addr = environ['REMOTE_ADDR']
277            else:
278                # mod_auth_tkt uses this dummy value when IP is not
279                # checked:
280                remote_addr = '0.0.0.0'
281            # @@: This should handle bad signatures better:
282            # Also, timeouts should cause cookie refresh
283            try:
284                timestamp, userid, tokens, user_data = parse_ticket(
285                    self.secret, cookie_value, remote_addr)
286                tokens = ','.join(tokens)
287                environ['REMOTE_USER'] = userid
288                if environ.get('REMOTE_USER_TOKENS'):
289                    # We want to add tokens/roles to what's there:
290                    tokens = environ['REMOTE_USER_TOKENS'] + ',' + tokens
291                environ['REMOTE_USER_TOKENS'] = tokens
292                environ['REMOTE_USER_DATA'] = user_data
293                environ['AUTH_TYPE'] = 'cookie'
294            except BadTicket:
295                # bad credentials, just ignore without logging the user
296                # in or anything
297                pass
298        set_cookies = []
299
300        def set_user(userid, tokens='', user_data=''):
301            set_cookies.extend(self.set_user_cookie(
302                environ, userid, tokens, user_data))
303
304        def logout_user():
305            set_cookies.extend(self.logout_user_cookie(environ))
306
307        environ['paste.auth_tkt.set_user'] = set_user
308        environ['paste.auth_tkt.logout_user'] = logout_user
309        if self.logout_path and environ.get('PATH_INFO') == self.logout_path:
310            logout_user()
311
312        def cookie_setting_start_response(status, headers, exc_info=None):
313            headers.extend(set_cookies)
314            return start_response(status, headers, exc_info)
315
316        return self.app(environ, cookie_setting_start_response)
317
318    def set_user_cookie(self, environ, userid, tokens, user_data):
319        if not isinstance(tokens, basestring):
320            tokens = ','.join(tokens)
321        if self.include_ip:
322            remote_addr = environ['REMOTE_ADDR']
323        else:
324            remote_addr = '0.0.0.0'
325        ticket = AuthTicket(
326            self.secret,
327            userid,
328            remote_addr,
329            tokens=tokens,
330            user_data=user_data,
331            cookie_name=self.cookie_name,
332            secure=self.secure)
333        # @@: Should we set REMOTE_USER etc in the current
334        # environment right now as well?
335        cur_domain = environ.get('HTTP_HOST', environ.get('SERVER_NAME'))
336        wild_domain = '.' + cur_domain
337
338        cookie_options = ""
339        if self.secure:
340            cookie_options += "; secure"
341        if self.httponly:
342            cookie_options += "; HttpOnly"
343
344        cookies = []
345        if self.no_domain_cookie:
346            cookies.append(('Set-Cookie', '%s=%s; Path=/%s' % (
347                self.cookie_name, ticket.cookie_value(), cookie_options)))
348        if self.current_domain_cookie:
349            cookies.append(('Set-Cookie', '%s=%s; Path=/; Domain=%s%s' % (
350                self.cookie_name, ticket.cookie_value(), cur_domain,
351                cookie_options)))
352        if self.wildcard_cookie:
353            cookies.append(('Set-Cookie', '%s=%s; Path=/; Domain=%s%s' % (
354                self.cookie_name, ticket.cookie_value(), wild_domain,
355                cookie_options)))
356
357        return cookies
358
359    def logout_user_cookie(self, environ):
360        cur_domain = environ.get('HTTP_HOST', environ.get('SERVER_NAME'))
361        wild_domain = '.' + cur_domain
362        expires = 'Sat, 01-Jan-2000 12:00:00 GMT'
363        cookies = [
364            ('Set-Cookie', '%s=""; Expires="%s"; Path=/' % (self.cookie_name, expires)),
365            ('Set-Cookie', '%s=""; Expires="%s"; Path=/; Domain=%s' %
366             (self.cookie_name, expires, cur_domain)),
367            ('Set-Cookie', '%s=""; Expires="%s"; Path=/; Domain=%s' %
368             (self.cookie_name, expires, wild_domain)),
369            ]
370        return cookies
371
372
373def make_auth_tkt_middleware(
374    app,
375    global_conf,
376    secret=None,
377    cookie_name='auth_tkt',
378    secure=False,
379    include_ip=True,
380    logout_path=None):
381    """
382    Creates the `AuthTKTMiddleware
383    <class-paste.auth.auth_tkt.AuthTKTMiddleware.html>`_.
384
385    ``secret`` is requird, but can be set globally or locally.
386    """
387    from paste.deploy.converters import asbool
388    secure = asbool(secure)
389    include_ip = asbool(include_ip)
390    if secret is None:
391        secret = global_conf.get('secret')
392    if not secret:
393        raise ValueError(
394            "You must provide a 'secret' (in global or local configuration)")
395    return AuthTKTMiddleware(
396        app, secret, cookie_name, secure, include_ip, logout_path or None)
Note: Veja TracBrowser para ajuda no uso do navegador do trac.
 

The contents and data of this website are published under license:
Creative Commons 4.0 Brasil - Atribuir Fonte - Compartilhar Igual.