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

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

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

行番号 
1# (c) 2005 Ben Bangert
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"""
5OpenID Authentication (Consumer)
6
7OpenID is a distributed authentication system for single sign-on originally
8developed at/for LiveJournal.com.
9
10    http://openid.net/
11
12URL. You can have multiple identities in the same way you can have multiple
13URLs. All OpenID does is provide a way to prove that you own a URL (identity).
14And it does this without passing around your password, your email address, or
15anything you don't want it to. There's no profile exchange component at all:
16your profiile is your identity URL, but recipients of your identity can then
17learn more about you from any public, semantically interesting documents
18linked thereunder (FOAF, RSS, Atom, vCARD, etc.).
19
20``Note``: paste.auth.openid requires installation of the Python-OpenID
21libraries::
22
23    http://www.openidenabled.com/
24
25This module is based highly off the consumer.py that Python OpenID comes with.
26
27Using the OpenID Middleware
28===========================
29
30Using the OpenID middleware is fairly easy, the most minimal example using the
31basic login form thats included::
32
33    # Add to your wsgi app creation
34    from paste.auth import open_id
35
36    wsgi_app = open_id.middleware(wsgi_app, '/somewhere/to/store/openid/data')
37
38You will now have the OpenID form available at /oid on your site. Logging in will
39verify that the login worked.
40
41A more complete login should involve having the OpenID middleware load your own
42login page after verifying the OpenID URL so that you can retain the login
43information in your webapp (session, cookies, etc.)::
44
45    wsgi_app = open_id.middleware(wsgi_app, '/somewhere/to/store/openid/data',
46                                  login_redirect='/your/login/code')
47
48Your login code should then be configured to retrieve 'paste.auth.open_id' for
49the users OpenID URL. If this key does not exist, the user has not logged in.
50
51Once the login is retrieved, it should be saved in your webapp, and the user
52should be redirected to wherever they would normally go after a successful
53login.
54"""
55
56__all__ = ['AuthOpenIDHandler']
57
58import cgi
59import urlparse
60import re
61
62import paste.request
63from paste import httpexceptions
64
65def quoteattr(s):
66    qs = cgi.escape(s, 1)
67    return '"%s"' % (qs,)
68
69# You may need to manually add the openid package into your
70# python path if you don't have it installed with your system python.
71# If so, uncomment the line below, and change the path where you have
72# Python-OpenID.
73# sys.path.append('/path/to/openid/')
74
75from openid.store import filestore
76from openid.consumer import consumer
77from openid.oidutil import appendArgs
78
79class AuthOpenIDHandler(object):
80    """
81    This middleware implements OpenID Consumer behavior to authenticate a
82    URL against an OpenID Server.
83    """
84
85    def __init__(self, app, data_store_path, auth_prefix='/oid',
86                 login_redirect=None, catch_401=False,
87                 url_to_username=None):
88        """
89        Initialize the OpenID middleware
90
91        ``app``
92            Your WSGI app to call
93           
94        ``data_store_path``
95            Directory to store crypto data in for use with OpenID servers.
96           
97        ``auth_prefix``
98            Location for authentication process/verification
99           
100        ``login_redirect``
101            Location to load after successful process of login
102           
103        ``catch_401``
104            If true, then any 401 responses will turn into open ID login
105            requirements.
106           
107        ``url_to_username``
108            A function called like ``url_to_username(environ, url)``, which should
109            return a string username.  If not given, the URL will be the username.
110        """
111        store = filestore.FileOpenIDStore(data_store_path)
112        self.oidconsumer = consumer.OpenIDConsumer(store)
113
114        self.app = app
115        self.auth_prefix = auth_prefix
116        self.data_store_path = data_store_path
117        self.login_redirect = login_redirect
118        self.catch_401 = catch_401
119        self.url_to_username = url_to_username
120
121    def __call__(self, environ, start_response):
122        if environ['PATH_INFO'].startswith(self.auth_prefix):
123            # Let's load everything into a request dict to pass around easier
124            request = dict(environ=environ, start=start_response, body=[])
125            request['base_url'] = paste.request.construct_url(environ, with_path_info=False,
126                                                              with_query_string=False)
127
128            path = re.sub(self.auth_prefix, '', environ['PATH_INFO'])
129            request['parsed_uri'] = urlparse.urlparse(path)
130            request['query'] = dict(paste.request.parse_querystring(environ))
131
132            path = request['parsed_uri'][2]
133            if path == '/' or not path:
134                return self.render(request)
135            elif path == '/verify':
136                return self.do_verify(request)
137            elif path == '/process':
138                return self.do_process(request)
139            else:
140                return self.not_found(request)
141        else:
142            if self.catch_401:
143                return self.catch_401_app_call(environ, start_response)
144            return self.app(environ, start_response)
145
146    def catch_401_app_call(self, environ, start_response):
147        """
148        Call the application, and redirect if the app returns a 401 response
149        """
150        was_401 = []
151        def replacement_start_response(status, headers, exc_info=None):
152            if int(status.split(None, 1)) == 401:
153                # @@: Do I need to append something to go back to where we
154                # came from?
155                was_401.append(1)
156                def dummy_writer(v):
157                    pass
158                return dummy_writer
159            else:
160                return start_response(status, headers, exc_info)
161        app_iter = self.app(environ, replacement_start_response)
162        if was_401:
163            try:
164                list(app_iter)
165            finally:
166                if hasattr(app_iter, 'close'):
167                    app_iter.close()
168            redir_url = paste.request.construct_url(environ, with_path_info=False,
169                                                    with_query_string=False)
170            exc = httpexceptions.HTTPTemporaryRedirect(redir_url)
171            return exc.wsgi_application(environ, start_response)
172        else:
173            return app_iter
174
175    def do_verify(self, request):
176        """Process the form submission, initating OpenID verification.
177        """
178
179        # First, make sure that the user entered something
180        openid_url = request['query'].get('openid_url')
181        if not openid_url:
182            return self.render(request, 'Enter an identity URL to verify.',
183                        css_class='error', form_contents=openid_url)
184
185        oidconsumer = self.oidconsumer
186
187        # Then, ask the library to begin the authorization.
188        # Here we find out the identity server that will verify the
189        # user's identity, and get a token that allows us to
190        # communicate securely with the identity server.
191        status, info = oidconsumer.beginAuth(openid_url)
192
193        # If the URL was unusable (either because of network
194        # conditions, a server error, or that the response returned
195        # was not an OpenID identity page), the library will return
196        # an error code. Let the user know that that URL is unusable.
197        if status in [consumer.HTTP_FAILURE, consumer.PARSE_ERROR]:
198            if status == consumer.HTTP_FAILURE:
199                fmt = 'Failed to retrieve <q>%s</q>'
200            else:
201                fmt = 'Could not find OpenID information in <q>%s</q>'
202
203            message = fmt % (cgi.escape(openid_url),)
204            return self.render(request, message, css_class='error', form_contents=openid_url)
205        elif status == consumer.SUCCESS:
206            # The URL was a valid identity URL. Now we construct a URL
207            # that will get us to process the server response. We will
208            # need the token from the beginAuth call when processing
209            # the response. A cookie or a session object could be used
210            # to accomplish this, but for simplicity here we just add
211            # it as a query parameter of the return-to URL.
212            return_to = self.build_url(request, 'process', token=info.token)
213
214            # Now ask the library for the URL to redirect the user to
215            # his OpenID server. It is required for security that the
216            # return_to URL must be under the specified trust_root. We
217            # just use the base_url for this server as a trust root.
218            redirect_url = oidconsumer.constructRedirect(
219                info, return_to, trust_root=request['base_url'])
220
221            # Send the redirect response
222            return self.redirect(request, redirect_url)
223        else:
224            assert False, 'Not reached'
225
226    def do_process(self, request):
227        """Handle the redirect from the OpenID server.
228        """
229        oidconsumer = self.oidconsumer
230
231        # retrieve the token from the environment (in this case, the URL)
232        token = request['query'].get('token', '')
233
234        # Ask the library to check the response that the server sent
235        # us.  Status is a code indicating the response type. info is
236        # either None or a string containing more information about
237        # the return type.
238        status, info = oidconsumer.completeAuth(token, request['query'])
239
240        css_class = 'error'
241        openid_url = None
242        if status == consumer.FAILURE and info:
243            # In the case of failure, if info is non-None, it is the
244            # URL that we were verifying. We include it in the error
245            # message to help the user figure out what happened.
246            openid_url = info
247            fmt = "Verification of %s failed."
248            message = fmt % (cgi.escape(openid_url),)
249        elif status == consumer.SUCCESS:
250            # Success means that the transaction completed without
251            # error. If info is None, it means that the user cancelled
252            # the verification.
253            css_class = 'alert'
254            if info:
255                # This is a successful verification attempt. If this
256                # was a real application, we would do our login,
257                # comment posting, etc. here.
258                openid_url = info
259                if self.url_to_username:
260                    username = self.url_to_username(request['environ'], openid_url)
261                else:
262                    username = openid_url
263                if 'paste.auth_tkt.set_user' in request['environ']:
264                    request['environ']['paste.auth_tkt.set_user'](username)
265                if not self.login_redirect:
266                    fmt = ("If you had supplied a login redirect path, you would have "
267                           "been redirected there.  "
268                           "You have successfully verified %s as your identity.")
269                    message = fmt % (cgi.escape(openid_url),)
270                else:
271                    # @@: This stuff doesn't make sense to me; why not a remote redirect?
272                    request['environ']['paste.auth.open_id'] = openid_url
273                    request['environ']['PATH_INFO'] = self.login_redirect
274                    return self.app(request['environ'], request['start'])
275                    #exc = httpexceptions.HTTPTemporaryRedirect(self.login_redirect)
276                    #return exc.wsgi_application(request['environ'], request['start'])
277            else:
278                # cancelled
279                message = 'Verification cancelled'
280        else:
281            # Either we don't understand the code or there is no
282            # openid_url included with the error. Give a generic
283            # failure message. The library should supply debug
284            # information in a log.
285            message = 'Verification failed.'
286
287        return self.render(request, message, css_class, openid_url)
288
289    def build_url(self, request, action, **query):
290        """Build a URL relative to the server base_url, with the given
291        query parameters added."""
292        base = urlparse.urljoin(request['base_url'], self.auth_prefix + '/' + action)
293        return appendArgs(base, query)
294
295    def redirect(self, request, redirect_url):
296        """Send a redirect response to the given URL to the browser."""
297        response_headers = [('Content-type', 'text/plain'),
298                            ('Location', redirect_url)]
299        request['start']('302 REDIRECT', response_headers)
300        return ["Redirecting to %s" % redirect_url]
301
302    def not_found(self, request):
303        """Render a page with a 404 return code and a message."""
304        fmt = 'The path <q>%s</q> was not understood by this server.'
305        msg = fmt % (request['parsed_uri'],)
306        openid_url = request['query'].get('openid_url')
307        return self.render(request, msg, 'error', openid_url, status='404 Not Found')
308
309    def render(self, request, message=None, css_class='alert', form_contents=None,
310               status='200 OK', title="Python OpenID Consumer"):
311        """Render a page."""
312        response_headers = [('Content-type', 'text/html')]
313        request['start'](str(status), response_headers)
314
315        self.page_header(request, title)
316        if message:
317            request['body'].append("<div class='%s'>" % (css_class,))
318            request['body'].append(message)
319            request['body'].append("</div>")
320        self.page_footer(request, form_contents)
321        return request['body']
322
323    def page_header(self, request, title):
324        """Render the page header"""
325        request['body'].append('''\
326<html>
327  <head><title>%s</title></head>
328  <style type="text/css">
329      * {
330        font-family: verdana,sans-serif;
331      }
332      body {
333        width: 50em;
334        margin: 1em;
335      }
336      div {
337        padding: .5em;
338      }
339      table {
340        margin: none;
341        padding: none;
342      }
343      .alert {
344        border: 1px solid #e7dc2b;
345        background: #fff888;
346      }
347      .error {
348        border: 1px solid #ff0000;
349        background: #ffaaaa;
350      }
351      #verify-form {
352        border: 1px solid #777777;
353        background: #dddddd;
354        margin-top: 1em;
355        padding-bottom: 0em;
356      }
357  </style>
358  <body>
359    <h1>%s</h1>
360    <p>
361      This example consumer uses the <a
362      href="http://openid.schtuff.com/">Python OpenID</a> library. It
363      just verifies that the URL that you enter is your identity URL.
364    </p>
365''' % (title, title))
366
367    def page_footer(self, request, form_contents):
368        """Render the page footer"""
369        if not form_contents:
370            form_contents = ''
371
372        request['body'].append('''\
373    <div id="verify-form">
374      <form method="get" action=%s>
375        Identity&nbsp;URL:
376        <input type="text" name="openid_url" value=%s />
377        <input type="submit" value="Verify" />
378      </form>
379    </div>
380  </body>
381</html>
382''' % (quoteattr(self.build_url(request, 'verify')), quoteattr(form_contents)))
383
384
385middleware = AuthOpenIDHandler
386
387def make_open_id_middleware(
388    app,
389    global_conf,
390    # Should this default to something, or inherit something from global_conf?:
391    data_store_path,
392    auth_prefix='/oid',
393    login_redirect=None,
394    catch_401=False,
395    url_to_username=None,
396    apply_auth_tkt=False,
397    auth_tkt_logout_path=None):
398    from paste.deploy.converters import asbool
399    from paste.util import import_string
400    catch_401 = asbool(catch_401)
401    if url_to_username and isinstance(url_to_username, basestring):
402        url_to_username = import_string.eval_import(url_to_username)
403    apply_auth_tkt = asbool(apply_auth_tkt)
404    new_app = AuthOpenIDHandler(
405        app, data_store_path=data_store_path, auth_prefix=auth_prefix,
406        login_redirect=login_redirect, catch_401=catch_401,
407        url_to_username=url_to_username or None)
408    if apply_auth_tkt:
409        from paste.auth import auth_tkt
410        new_app = auth_tkt.make_auth_tkt_middleware(
411            new_app, global_conf, logout_path=auth_tkt_logout_path)
412    return new_app
Note: リポジトリブラウザについてのヘルプは TracBrowser を参照してください。