[openid-test commit] r45 - trunk/janrain-python

1 view
Skip to first unread message

codesite...@google.com

unread,
Feb 7, 2008, 3:15:42 AM2/7/08
to openi...@googlegroups.com
Author: heaven
Date: Thu Feb 7 00:15:06 2008
New Revision: 45

Modified:
trunk/janrain-python/janrain_python.py

Log:
more interop, 1.1 vs 2.0 stuff. up and serving at
http://snarfed.org/cgi-sys/cgiwrap/ryanb/janrain-python.cgi .

bugs remaining:
- 1.1 RP fails on stateless mode auth
- 1.1 OP doesn't handle checkid_immediate requests for the will-setup-*
identities. it needs to set openid.user_setup_url.


Modified: trunk/janrain-python/janrain_python.py
==============================================================================
--- trunk/janrain-python/janrain_python.py (original)
+++ trunk/janrain-python/janrain_python.py Thu Feb 7 00:15:06 2008
@@ -9,14 +9,20 @@

Responds to the following URLs:

-http://janrain-python-test.openid.net/<protocol>/identity/will-sign
-http://janrain-python-test.openid.net/<protocol>/identity/will-setup-then-sign
-http://janrain-python-test.openid.net/<protocol>/identity/will-setup-then-cancel
-http://janrain-python-test.openid.net/<protocol>/rp
+ .../caps
+ .../<protocol>/identity/will-sign
+ .../<protocol>/identity/will-setup-then-sign
+ .../<protocol>/identity/will-setup-then-cancel
+ .../<protocol>/rp

where <protocol> is "1.1" or "2.0" or "1.1,2.0". Make sure your realm has
"-test" or "openid-test" in its path.

+To install, make openid_1_0_0 and openid_2_1_1 symlinks, pointing to the
+respective versions of the janrain openid librari, in the same
directory as
+this file. Then, configure your web server to run this file as a CGI script,
+which usually requires the executable bit to be set.
+
For more about OpenID, see:
http://openid.net/
http://openid.net/about.bml
@@ -44,10 +50,13 @@
import wsgiref.handlers
import wsgiref.util

-from openid.server.server import Server as OpenIDServer
-from openid.store import filestore
-from openid.consumer import discover
-from openid.consumer.consumer import Consumer as OpenIDConsumer
+# these are the openid.server.server, openid.consumer.consumer, and
+# openid.store.filestore modules. they're imported from openid_1_0_0 or
+# openid_2_1_1 in main(), depending on the version in the URL.
+server = None
+consumer = None
+filestore = None
+dumbstore = None

logging.getLogger().addHandler(logging.StreamHandler())
logging.getLogger().setLevel(logging.DEBUG)
@@ -55,6 +64,14 @@
# supported openid protocol versions
PROTOCOL_VERSIONS = ('1.1', '2.0', '1.1,2.0')

+# name of session argument for 1.0.0 library's token
+TOKEN_ARG = '_openid_consumer_.last_token'
+
+# TODO: remove
+# name of query parameter for the http method; used when emulating the 2.1.1
+# library with 1.0.0.
+# HTTP_METHOD_ARG = '__http_method'
+
# trust root (realm) to send to provider when acting as consumer
TRUST_ROOT = 'http://' + os.environ['SERVER_NAME']
if os.environ['SERVER_PORT'] != '80':
@@ -186,7 +203,7 @@
# Based on JanRain's Python OpenID library, version 2.1.1.
# See http://www.openidenabled.com/python-openid/

-#openid1.1
+openid1.1
openid2.0
# xri
"""
@@ -241,6 +258,7 @@

headers = encoded_response.headers.items()
headers.append(('Content-Type', 'text/plain'))
+ print >> sys.stderr, headers
self.start_response(status_str, headers)

logging.debug('Sending encoded response: %s' % encoded_response)
@@ -306,31 +324,33 @@
Handler.__init__(self, environ, start_response)
self.checkid_immediate = checkid_immediate
self.checkid_setup = checkid_setup
- self.openid_server = OpenIDServer(filestore.FileOpenIDStore('.'), self.uri)
+ self.openid_server = server.Server(filestore.FileOpenIDStore('.'), self.uri)

def post(self):
"""Handles associate and check_authentication requests."""
oidrequest = self.get_openid_request()
- if oidrequest:
- if oidrequest.mode in ('associate', 'check_authentication'):
- self.respond(self.openid_server.handleRequest(oidrequest))
- else:
- self.render(ERROR, 'Expected an OpenID request', '404 Not found')
+ if oidrequest and oidrequest.mode in ('associate', 'check_authentication'):
+ self.respond(self.openid_server.handleRequest(oidrequest))
+ elif oidrequest is False:
+ msg = 'Expected associate or check_authentication, got %s' % oidrequest.mode
+ self.render(ERROR, msg, '404 Not found')
+ # else get_openid_request() rendered the error page

def get(self):
"""Handles checkid_* requests."""
oidrequest = self.get_openid_request()
if oidrequest and oidrequest.mode in ('checkid_immediate', 'checkid_setup'):
- self.respond(oidrequest.answer(getattr(self, oidrequest.mode)))
+ self.respond(oidrequest.answer(getattr(self, oidrequest.mode)))
elif oidrequest is False:
Handler.get(self)
+ # else get_openid_request() rendered the error page


class Sign(Provider):
- """Signs a requests and verifies the signature."""
+ """Signs a request and verifies the signature."""
def __init__(self, environ, start_response):
"""The keyword argument values determine how we respond to each mode."""
- Provider.__init__(self, environ, start_response, checkid_immediate=False,
+ Provider.__init__(self, environ, start_response, checkid_immediate=True,
checkid_setup=True)


@@ -371,19 +391,18 @@
filestore._removeIfPresent(file)
store = filestore.FileOpenIDStore('.')
elif assoc_mode == 'stateless':
- store = None
+ store = dumbstore.DumbStore('passphrase')
else:
assert False, 'bad assoc_mode value: %s' % assoc_mode

# retrieve the session from the cookie, if available. obviously
not secure.
session = self.get_session_from_cookie()
- self.openid_consumer = OpenIDConsumer(session, store)
+ self.openid_consumer = consumer.Consumer(session, store)

def make_session_cookie(self):
"""Pickles the session and returns it as a cookie value."""
expires = datetime.datetime.now() + datetime.timedelta(minutes=5)
expires_rfc822 = expires.strftime('%a, %d %b %Y %H:%M:%S -0800')
-
encoded = urllib.quote(cPickle.dumps(self.openid_consumer.session))
return 'janrain-python-session=%s; expires=%s' % (encoded, expires_rfc822)

@@ -400,13 +419,6 @@

return {}

- def render(self, *args, **kwargs):
- """Adds the session cookie header, then passes through to Handler.render."""
- # *optionally* stores the session in a cookie. if the client doesn't
- # support cookies, no matter, the consumer flow will still work.
- kwargs['headers'] = [('Set-Cookie', self.make_session_cookie())]
- Handler.render(self, *args, **kwargs)
-
def get(self):
"""Handles consumer GET requests."""
args = self.arg_dict()
@@ -428,6 +440,10 @@
# start with discovery
auth_request = self.openid_consumer.begin(args['openid_identifier'])

+ # *optionally* stores the session in a cookie. if the client doesn't
+ # support cookies, no matter, the consumer flow will still work.
+ headers = [('Set-Cookie', self.make_session_cookie())]
+
# this is a form post. what to do...?
if args.get('op') == 'disco':
# return the discovery details
@@ -439,7 +455,8 @@
discovered = {'claimed_id': claimed_id,
'openid_provider': auth_request.endpoint.server_url,
'local_id': local_id}
- self.render(DISCOVERY, content_type='text/plain', data=discovered)
+ self.render(DISCOVERY, content_type= 'text/plain', data=discovered,
+ headers=headers)
return

elif args.get('op') == 'assoc':
@@ -450,14 +467,14 @@
else:
assoc = {'handle': '', 'type': ''}

- self.render(ASSOCIATION, content_type='text/plain', data=assoc)
+ self.render(ASSOCIATION, content_type='text/plain', data=assoc,
+ headers=headers)

else:
# start the flow!
return_to = self.uri + '.return_to'
redirect_to = auth_request.redirectURL(TRUST_ROOT, return_to)
- headers = [('Location', redirect_to),
- ('Set-Cookie', self.make_session_cookie())]
+ headers += [('Location', redirect_to)]
self.start_response('302 Found', headers)

else:
@@ -486,14 +503,148 @@
if match and method in ('GET', 'POST'):
# TODO(ryanb); ugh, this is awful. make this not awful.
caps, openid_version, action = match.groups()
+
if caps == 'caps':
action = 'caps'
+ else:
+ import_libs(openid_version)
if action in handlers:
handler_cls = handlers[action]

handler = handler_cls(environ, start_response)
getattr(handler, method.lower())()
return handler.output
+
+
+def import_libs(openid_version):
+ """Import the the JanRain libraries for the given OpenID version.
+
+ The API changed between 1.0.0 and 2.1.1, so if this imports 1.0.0,
it also
+ fixes it up with a compatibility wrapper. This lets the rest of the code
+ treat it like 2.1.1, regardless of which version it actually is.
+
+ The openid_version arg should be '1.1', '2.0', or '1.1,2.0'.
+ """
+ global server
+ global consumer
+ global filestore
+ global dumbstore
+
+ # generally useful, easily initialized struct class
+ class Struct:
+ def __init__(self, **kwargs):
+ for arg, val in kwargs.items():
+ setattr(self, arg, val)
+
+ # point to the requested version of the janrain libraries
+ if openid_version in ('2.0', '1.1,2.0'):
+ import openid_2_1_1
+ sys.modules['openid'] = openid_2_1_1
+ from openid_2_1_1.server import server
+ from openid_2_1_1.consumer import consumer
+ from openid_2_1_1.store import filestore
+
+ # dumbstore was removed in 2.1.1. add a shim.
+ dumbstore = Struct(DumbStore=lambda passphrase: None)
+
+ else:
+ assert openid_version == '1.1'
+ import openid_1_0_0
+ sys.modules['openid'] = openid_1_0_0
+ from openid_1_0_0.server import server
+ from openid_1_0_0.consumer import consumer
+ from openid_1_0_0.store import filestore
+ from openid_1_0_0.store import dumbstore
+
+ # OpenIDConsumer => Consumer, OpenIDServer => Server
+ consumer.Consumer = consumer.OpenIDConsumer
+ server.Server = server.OpenIDServer
+
+ # Consumer.beginAuth() => Consumer.begin()
+ def consumer_begin(self, *args, **kwargs):
+ status, auth_req = consumer.Consumer.beginAuth(self, *args, **kwargs)
+ assert auth_req, status
+ self.session[TOKEN_ARG] = auth_req.token
+ auth_req.endpoint = auth_req
+ auth_req.local_id = ''
+ auth_req.claimed_id = auth_req.server_id
+ auth_req.assoc = self.store.getAssociation(auth_req.server_url)
+ auth_req.redirectURL = (lambda trust_root, return_to:
+ self.constructRedirect(auth_req, return_to, trust_root))
+ return auth_req
+ consumer.Consumer.begin = consumer_begin
+
+ # Consumer.completeAuth() => Consumer.complete()
+ def complete(self, args, uri):
+ status, message = self.completeAuth(self.session[TOKEN_ARG], args)
+ if status == consumer.SUCCESS:
+ if message:
+ status = 'success'
+ else:
+ status = 'cancel'
+ elif status == consumer.SETUP_NEEDED:
+ status = 'setup_needed'
+ return Struct(status=status, message=message)
+ consumer.Consumer.complete = complete
+
+ # Consumer.__init__() added session arg
+ orig_consumer_init = consumer.Consumer.__init__
+ def consumer_init(self, session, *args, **kwargs):
+ orig_consumer_init(self, *args, **kwargs)
+ self.session = session
+ consumer.Consumer.__init__ = consumer_init
+
+ # Consumer.__init__()'s positional args swapped. (wtf! i mean, really.)
+ orig_server_init = server.Server.__init__
+ server.Server.__init__ = (lambda self, store, endpoint:
+ orig_server_init(self, endpoint, store))
+
+ # no separate decodeRequest() step or Request class in 1.0.0;
fabricate one
+ class Request:
+ def __init__(self, server, args):
+ self.server = server
+ self.args = args
+ self.mode = args['openid.mode']
+
+ def answer(self, allow):
+ return self.server.handleRequest(self, allow)
+
+ def decodeRequest(self, args):
+ if args.get('openid.mode'):
+ return Request(self, args)
+ else:
+ return None
+ server.Server.decodeRequest = decodeRequest
+
+ # Server.getOpenIDResponse() => server.handleRequest()
+ def handleRequest(self, req, allow=True):
+ method = os.environ['REQUEST_METHOD']
+ status, val = self.getOpenIDResponse(method, req.args, lambda x,
y: allow)
+ assert status != server.LOCAL_ERROR, val
+
+ responses = {server.REDIRECT: (302, val),
+ # see 1.0.0 server.py:461
+ server.DO_AUTH: (200, val),
+ server.DO_ABOUT: (200, 'DO_ABOUT'),
+ server.REMOTE_OK: (200, val),
+ server.REMOTE_ERROR: (400, val),
+ }
+ code, body = responses[status]
+ if status == server.DO_AUTH:
+ if allow:
+ body = req.args['openid.return_to']
+ else:
+ body = val.getCancelURL()
+
+ headers = {}
+ if code == 302:
+ headers['Location'] = body
+ return Struct(code=code, body=body, headers=headers)
+
+ server.Server.handleRequest = handleRequest
+
+ # ignore encoding for now
+ server.Server.encodeResponse = lambda self, response: response


def main(argv):

Reply all
Reply to author
Forward
0 new messages