1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1.2 +++ b/XSLForms/Resources/OpenIDLogin.py Mon Nov 19 00:18:17 2007 +0000
1.3 @@ -0,0 +1,219 @@
1.4 +#!/usr/bin/env python
1.5 +
1.6 +"""
1.7 +OpenID Login resources for XSLForms applications. These resources use "root"
1.8 +attributes on transaction objects, and therefore should be defined within the
1.9 +appropriate resources in site maps.
1.10 +
1.11 +Copyright (C) 2006, 2007 Paul Boddie <paul@boddie.org.uk>
1.12 +
1.13 +This program is free software; you can redistribute it and/or modify it under
1.14 +the terms of the GNU Lesser General Public License as published by the Free
1.15 +Software Foundation; either version 3 of the License, or (at your option) any
1.16 +later version.
1.17 +
1.18 +This program is distributed in the hope that it will be useful, but WITHOUT
1.19 +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
1.20 +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
1.21 +details.
1.22 +
1.23 +You should have received a copy of the GNU Lesser General Public License along
1.24 +with this program. If not, see <http://www.gnu.org/licenses/>.
1.25 +"""
1.26 +
1.27 +from WebStack.Generic import ContentType, EndOfResponse
1.28 +from WebStack.Resources.OpenIDLogin import OpenIDLoginUtils
1.29 +from XSLForms.Resources.WebResources import XSLFormsResource
1.30 +
1.31 +import WebStack.Resources.OpenIDRedirect # LoginRedirectResource
1.32 +
1.33 +class OpenIDLoginResource(XSLFormsResource, OpenIDLoginUtils):
1.34 +
1.35 + """
1.36 + A login screen resource which should be modified or subclassed to define the
1.37 + following attributes:
1.38 +
1.39 + * resource_dir
1.40 + * template_resources - including a "login" entry for the login screen and
1.41 + a "success" entry for a screen indicating a
1.42 + successful login (used when redirects are not in
1.43 + use)
1.44 + * document_resources - including a "translations" entry
1.45 +
1.46 + The latter attribute is optional.
1.47 +
1.48 + The login template must define a "login" action, and provide a document
1.49 + structure where the login credentials can be found through this class's
1.50 + 'path_to_login_element' attribute (which can be overridden or modified).
1.51 + Such a structure would be as follows for the default configuration:
1.52 +
1.53 + <login username="..." password="..."/>
1.54 +
1.55 + The success template must provide a document structure where the location of
1.56 + the application can be found through this class's 'path_to_success_element'
1.57 + attribute (which can be overridden or modified). Such a structure would be
1.58 + as follows for the default configuration:
1.59 +
1.60 + <success location="..."/>
1.61 + """
1.62 +
1.63 + path_to_login_element = "/login"
1.64 + path_to_success_element = "/success"
1.65 +
1.66 + def __init__(self, app_url, authenticator, associations=None, use_redirect=1):
1.67 +
1.68 + """
1.69 + Initialise the resource with an 'app_url' and an 'authenticator'.
1.70 +
1.71 + The optional 'associations' is a mapping from association handles to
1.72 + secret keys.
1.73 +
1.74 + If the optional 'use_redirect' flag is set to a false value (which is
1.75 + not the default), a confirmation screen is given instead of immediately
1.76 + redirecting the user back to the original application.
1.77 +
1.78 + To get the root of the application, this resource needs an attribute on
1.79 + the transaction called "root".
1.80 + """
1.81 +
1.82 + OpenIDLoginUtils.__init__(self, associations, use_redirect)
1.83 + self.app_url = app_url
1.84 + self.authenticator = authenticator
1.85 +
1.86 + def select_activity(self, trans, form):
1.87 + form.set_activity("login")
1.88 +
1.89 + def respond_to_input(self, trans, form):
1.90 + parameters = form.get_parameters()
1.91 +
1.92 + # Test for login.
1.93 +
1.94 + if parameters.has_key("login"):
1.95 + self.check_login(trans, form)
1.96 +
1.97 + # Check for an OpenID signature verification request.
1.98 +
1.99 + elif parameters.get("openid.mode", [None])[0] == "check_authentication":
1.100 + self.check_authentication(trans, trans.get_fields())
1.101 +
1.102 + # NOTE: Permit association requests here.
1.103 + # Otherwise, show the login form.
1.104 +
1.105 + else:
1.106 + self.show_login(trans, form)
1.107 +
1.108 + # Methods called by the OpenID logic.
1.109 +
1.110 + def check_login(self, trans, form):
1.111 + doc = form.get_document()
1.112 + parameters = form.get_parameters()
1.113 +
1.114 + logelem = doc.xpath(self.path_to_login_element)[0]
1.115 + return_to = logelem.getAttribute("return_to") or parameters.get("openid.return_to", [""])[0]
1.116 + claimed_id = logelem.getAttribute("claimed_id") or parameters.get("openid.claimed_id", [""])[0]
1.117 + local_id = logelem.getAttribute("identity") or parameters.get("openid.identity", [""])[0]
1.118 +
1.119 + username = logelem.getAttribute("username")
1.120 + password = logelem.getAttribute("password")
1.121 +
1.122 + # If successful, switch to the success template and redirect.
1.123 + # NOTE: Permit flexibility in the credentials.
1.124 +
1.125 + if self.authenticator.authenticate(trans, (local_id, username), password):
1.126 + endpoint = self.app_url + trans.get_path_without_query()
1.127 + self.redirect_to_application(trans, form, claimed_id, local_id, username, return_to, endpoint)
1.128 + else:
1.129 + error = doc.createElement("error")
1.130 + logelem.appendChild(error)
1.131 + error.setAttribute("message", "Username or password not valid")
1.132 + self.show_login(trans, form)
1.133 +
1.134 + def redirect_to_application(self, trans, form, claimed_id, local_id, username, return_to, endpoint):
1.135 +
1.136 + """
1.137 + Redirect the client using 'trans', 'claimed_id', 'local_id', 'username'
1.138 + and the given 'return_to' and 'endpoint' details.
1.139 + """
1.140 +
1.141 + fields = self.get_openid_fields(trans, claimed_id, local_id, username, return_to, endpoint)
1.142 + url = self.get_openid_url(trans, fields)
1.143 +
1.144 + # Show the success page anyway.
1.145 + # Offer a POST-based form for redirection.
1.146 +
1.147 + self.show_success(trans, form, fields)
1.148 + if self.use_redirect:
1.149 + trans.redirect(url)
1.150 +
1.151 + def show_login(self, trans, form):
1.152 +
1.153 + """
1.154 + Writes a login screen using the transaction 'trans' and 'form',
1.155 + including details of the 'return_to' URL which the client was attempting
1.156 + to access, along with the 'claimed_id' and 'local_id'.
1.157 + """
1.158 +
1.159 + doc = form.get_document()
1.160 + parameters = form.get_parameters()
1.161 +
1.162 + logelem = doc.xpath(self.path_to_login_element)[0]
1.163 + return_to = logelem.getAttribute("return_to") or parameters.get("openid.return_to", [""])[0]
1.164 + claimed_id = logelem.getAttribute("claimed_id") or parameters.get("openid.claimed_id", [""])[0]
1.165 + local_id = logelem.getAttribute("identity") or parameters.get("openid.identity", [""])[0]
1.166 +
1.167 + logelem = doc.xpath(self.path_to_login_element)[0]
1.168 + logelem.setAttribute("return_to", return_to)
1.169 + logelem.setAttribute("claimed_id", claimed_id)
1.170 + logelem.setAttribute("identity", local_id)
1.171 +
1.172 + def show_success(self, trans, form, fields):
1.173 +
1.174 + """
1.175 + Writes a success screen using the transaction 'trans' and 'form', using
1.176 + a dictionary of 'fields' providing details of the transaction.
1.177 + """
1.178 +
1.179 + # Switch to the success activity.
1.180 +
1.181 + form.set_activity("success")
1.182 + doc = form.new_instance("success")
1.183 + successelem = doc.xpath(self.path_to_success_element)[0]
1.184 + successelem.setAttribute("location", fields["openid.return_to"][0])
1.185 +
1.186 + # Add OpenID fields.
1.187 +
1.188 + for name, values in fields.items():
1.189 + field = doc.createElement("field")
1.190 + field.setAttribute("name", name)
1.191 + field.setAttribute("value", values[0])
1.192 + successelem.appendChild(field)
1.193 +
1.194 + form.set_document(doc)
1.195 +
1.196 + # Output preparation.
1.197 +
1.198 + def create_output(self, trans, form):
1.199 + attributes = trans.get_attributes()
1.200 +
1.201 + stylesheet_parameters = {}
1.202 + references = {}
1.203 +
1.204 + # Set up translations.
1.205 +
1.206 + if self.document_resources.has_key("translations"):
1.207 + translations_xml = self.prepare_document("translations")
1.208 +
1.209 + try:
1.210 + language = trans.get_content_languages()[0]
1.211 + except IndexError:
1.212 + language = "en"
1.213 +
1.214 + stylesheet_parameters["locale"] = language
1.215 + references["translations"] = translations_xml
1.216 +
1.217 + # Complete the response.
1.218 +
1.219 + stylesheet_parameters["root"] = attributes["root"]
1.220 + XSLFormsResource.create_output(self, trans, form, stylesheet_parameters=stylesheet_parameters, references=references)
1.221 +
1.222 +# vim: tabstop=4 expandtab shiftwidth=4