1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1.2 +++ b/WebStack/Resources/OpenIDInitiation.py Mon Nov 12 00:51:34 2007 +0000
1.3 @@ -0,0 +1,223 @@
1.4 +#!/usr/bin/env python
1.5 +
1.6 +"""
1.7 +OpenID initiation resources which redirect clients to an OpenID provider.
1.8 +
1.9 +Copyright (C) 2004, 2005, 2006, 2007 Paul Boddie <paul@boddie.org.uk>
1.10 +
1.11 +This library is free software; you can redistribute it and/or
1.12 +modify it under the terms of the GNU Lesser General Public
1.13 +License as published by the Free Software Foundation; either
1.14 +version 2.1 of the License, or (at your option) any later version.
1.15 +
1.16 +This library is distributed in the hope that it will be useful,
1.17 +but WITHOUT ANY WARRANTY; without even the implied warranty of
1.18 +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
1.19 +Lesser General Public License for more details.
1.20 +
1.21 +You should have received a copy of the GNU Lesser General Public
1.22 +License along with this library; if not, write to the Free Software
1.23 +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
1.24 +"""
1.25 +
1.26 +import WebStack.Generic
1.27 +import libxml2dom
1.28 +
1.29 +class OpenIDInitiationResource:
1.30 +
1.31 + "A resource providing an OpenID initiation screen."
1.32 +
1.33 + encoding = "utf-8"
1.34 + openid_ns = "http://specs.openid.net/auth/2.0"
1.35 +
1.36 + def __init__(self, openid_mode=None, use_redirect=1, urlencoding=None, encoding=None):
1.37 +
1.38 + """
1.39 + Initialise the resource.
1.40 +
1.41 + The optional 'openid_mode' parameter may be set to "checkid_immediate"
1.42 + or "checkid_setup" (the default).
1.43 +
1.44 + If the optional 'use_redirect' flag is set to 0, a confirmation screen
1.45 + is given instead of redirecting the user back to the original
1.46 + application.
1.47 +
1.48 + The optional 'urlencoding' parameter allows a special encoding to be
1.49 + used in producing the redirection path.
1.50 +
1.51 + The optional 'encoding' parameter allows a special encoding to be used
1.52 + in producing the initiation pages.
1.53 +
1.54 + To change the pages employed by this resource, either redefine the
1.55 + 'initiation_page' and 'success_page' attributes in instances of this class or
1.56 + a subclass, or override the 'show_initiation' and 'show_success' methods.
1.57 + """
1.58 +
1.59 + self.openid_mode = openid_mode or "checkid_setup"
1.60 + self.use_redirect = use_redirect
1.61 + self.urlencoding = urlencoding
1.62 + self.encoding = encoding or self.encoding
1.63 +
1.64 + def respond(self, trans):
1.65 +
1.66 + "Respond using the transaction 'trans'."
1.67 +
1.68 + app = get_target(trans, self.urlencoding, self.encoding)
1.69 +
1.70 + # Check for a submitted initiation form.
1.71 +
1.72 + fields_body = trans.get_fields_from_body(self.encoding)
1.73 +
1.74 + if fields_body.has_key("initiate") and fields_body.has_key("identity"):
1.75 + claimed_identifier, provider, local_identifier = self.get_provider_url(fields_body["identity"][0])
1.76 + if provider is not None:
1.77 + self._redirect(trans, app, claimed_identifier, provider, local_identifier)
1.78 + # The above method does not return.
1.79 +
1.80 + # Otherwise, show the initiation form.
1.81 +
1.82 + self.show_initiation(trans, app)
1.83 +
1.84 + def _redirect(self, trans, app, claimed_identifier, provider, local_identifier):
1.85 +
1.86 + """
1.87 + Redirect the client using 'trans' and the given 'app',
1.88 + 'claimed_identifier', 'provider' and 'local_identifier' details.
1.89 +
1.90 + See:
1.91 + http://openid.net/specs/openid-authentication-2_0-12.html#rfc.section.5.2
1.92 + http://openid.net/specs/openid-authentication-2_0-12.html#rfc.section.9
1.93 + """
1.94 +
1.95 + # NOTE: Should consider the special "select" mode for identity.
1.96 +
1.97 + url = "%s?openid.ns=%s&openid.mode=%s&openid.return_to=%s&openid.claimed_id=%s&openid.identity=%s" % (
1.98 + provider,
1.99 + trans.encode_path(self.openid_ns, self.urlencoding),
1.100 + trans.encode_path(self.openid_mode, self.urlencoding),
1.101 + trans.encode_path(app, self.urlencoding),
1.102 + trans.encode_path(claimed_identifier, self.urlencoding),
1.103 + trans.encode_path(local_identifier, self.urlencoding)
1.104 + )
1.105 +
1.106 + # Show the success page anyway.
1.107 +
1.108 + self.show_success(trans, url)
1.109 +
1.110 + # Redirect to the OpenID provider URL.
1.111 +
1.112 + if self.use_redirect:
1.113 + trans.redirect(url)
1.114 + else:
1.115 + raise WebStack.Generic.EndOfResponse
1.116 +
1.117 + def get_provider_url(self, identity):
1.118 +
1.119 + """
1.120 + Return the claimed identifier, provider URL and local identifier for the
1.121 + authenticating user using the given 'identity'.
1.122 +
1.123 + See:
1.124 + http://openid.net/specs/openid-authentication-2_0-12.html#rfc.section.7.3
1.125 + """
1.126 +
1.127 + if identity.startswith("xri://"):
1.128 + identity = openid[6:]
1.129 +
1.130 + # NOTE: Not yet discovering XRI providers.
1.131 +
1.132 + if identity[0] in ("=", "@", "+", "$", "!", "("):
1.133 + pass
1.134 + else:
1.135 + if not identity.startswith("http"):
1.136 + identity = "http://" + identity
1.137 +
1.138 + # Obtain a provider url from a resource at the stated URL.
1.139 +
1.140 + doc = libxml2dom.parseURI(identity, html=1)
1.141 + provider_links = doc.xpath("/html/head/link[contains(@rel, 'openid2.provider')]/@href")
1.142 + local_ids = doc.xpath("/html/head/link[contains(@rel, 'openid2.local_id')]/@href")
1.143 + if provider_links:
1.144 + if local_ids:
1.145 + return identity, provider_links[0].nodeValue, local_ids[0].nodeValue
1.146 + else:
1.147 + return identity, provider_links[0].nodeValue, None
1.148 +
1.149 + return identity, None, None
1.150 +
1.151 + def show_initiation(self, trans, app):
1.152 +
1.153 + """
1.154 + Writes a initiation screen using the transaction 'trans', including details
1.155 + of the 'app' which the client was attempting to access.
1.156 + """
1.157 +
1.158 + trans.set_content_type(WebStack.Generic.ContentType("text/html", self.encoding))
1.159 + out = trans.get_response_stream()
1.160 + out.write(self.initiation_page % app)
1.161 +
1.162 + def show_success(self, trans, url):
1.163 +
1.164 + """
1.165 + Writes a success screen using the transaction 'trans', including details
1.166 + of the OpenID provider 'url'.
1.167 + """
1.168 +
1.169 + trans.set_content_type(WebStack.Generic.ContentType("text/html", self.encoding))
1.170 + out = trans.get_response_stream()
1.171 + out.write(self.success_page % (url, url))
1.172 +
1.173 + initiation_page = """
1.174 +<html>
1.175 + <head>
1.176 + <title>Authenticate via OpenID</title>
1.177 + </head>
1.178 + <body>
1.179 + <h1>Authenticate via OpenID</h1>
1.180 + <form method="POST" name="openid_identifier">
1.181 + <p>OpenID Identifier (URL): <input name="identity" type="text" size="32"/></p>
1.182 + <p><input name="initiate" type="submit" value="Login"/></p>
1.183 + <input name="app" type="hidden" value="%s"/>
1.184 + </form>
1.185 + </body>
1.186 +</html>
1.187 +"""
1.188 +
1.189 + success_page = """
1.190 +<html>
1.191 + <head>
1.192 + <title>Authenticate via OpenID</title>
1.193 + </head>
1.194 + <body>
1.195 + <h1>Authenticate via OpenID</h1>
1.196 + <p>Please proceed to the OpenID provider: <a href="%s">%s</a>.</p>
1.197 + </body>
1.198 +</html>
1.199 +"""
1.200 +
1.201 +# General functions.
1.202 +
1.203 +def get_target(trans, urlencoding=None, encoding=None):
1.204 +
1.205 + """
1.206 + Return the application for 'trans' using the optional 'urlencoding' (or path
1.207 + encoding) and request body 'encoding'.
1.208 + """
1.209 +
1.210 + fields_path = trans.get_fields_from_path(urlencoding)
1.211 + fields_body = trans.get_fields_from_body(encoding)
1.212 +
1.213 + # NOTE: Handle missing redirects better.
1.214 +
1.215 + if fields_body.has_key("app"):
1.216 + apps = fields_body["app"]
1.217 + app = apps[0]
1.218 + elif fields_path.has_key("app"):
1.219 + apps = fields_path["app"]
1.220 + app = apps[0]
1.221 + else:
1.222 + app = u""
1.223 +
1.224 + return app
1.225 +
1.226 +# vim: tabstop=4 expandtab shiftwidth=4