paulb@651 | 1 | #!/usr/bin/env python |
paulb@651 | 2 | |
paulb@651 | 3 | """ |
paulb@651 | 4 | OpenID initiation resources for XSLForms applications. These resources use |
paulb@651 | 5 | "root" attributes on transaction objects, and therefore should be defined within |
paulb@651 | 6 | the appropriate resources in site maps. |
paulb@651 | 7 | |
paulb@651 | 8 | Copyright (C) 2006, 2007, 2008 Paul Boddie <paul@boddie.org.uk> |
paulb@651 | 9 | |
paulb@651 | 10 | This program is free software; you can redistribute it and/or modify it under |
paulb@651 | 11 | the terms of the GNU Lesser General Public License as published by the Free |
paulb@651 | 12 | Software Foundation; either version 3 of the License, or (at your option) any |
paulb@651 | 13 | later version. |
paulb@651 | 14 | |
paulb@651 | 15 | This program is distributed in the hope that it will be useful, but WITHOUT |
paulb@651 | 16 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
paulb@651 | 17 | FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more |
paulb@651 | 18 | details. |
paulb@651 | 19 | |
paulb@651 | 20 | You should have received a copy of the GNU Lesser General Public License along |
paulb@651 | 21 | with this program. If not, see <http://www.gnu.org/licenses/>. |
paulb@651 | 22 | """ |
paulb@651 | 23 | |
paulb@651 | 24 | from WebStack.Generic import ContentType, EndOfResponse |
paulb@651 | 25 | from WebStack.Resources.OpenIDInitiation import OpenIDInitiationUtils |
paulb@651 | 26 | from XSLForms.Resources.WebResources import XSLFormsResource |
paulb@651 | 27 | |
paulb@651 | 28 | class OpenIDInitiationResource(XSLFormsResource, OpenIDInitiationUtils): |
paulb@651 | 29 | |
paulb@651 | 30 | """ |
paulb@651 | 31 | An initiation screen resource which should be modified or subclassed to |
paulb@651 | 32 | define the following attributes: |
paulb@651 | 33 | |
paulb@651 | 34 | * resource_dir |
paulb@651 | 35 | * template_resources - including an "initiation" entry for the initiation |
paulb@651 | 36 | screen and a "success" entry for a screen |
paulb@651 | 37 | indicating successful redirection to a provider |
paulb@651 | 38 | (used when redirects are not in use) |
paulb@651 | 39 | * document_resources - including a "translations" entry |
paulb@651 | 40 | |
paulb@651 | 41 | The latter attribute is optional. |
paulb@651 | 42 | |
paulb@651 | 43 | The initiation template must define an "initiation" action, and provide a |
paulb@651 | 44 | document structure where the identity credentials can be found through this |
paulb@651 | 45 | class's 'path_to_identity_element' attribute (which can be overridden or |
paulb@651 | 46 | modified). Such a structure would be as follows for the default |
paulb@651 | 47 | configuration: |
paulb@651 | 48 | |
paulb@651 | 49 | <initiation identity="..."/> |
paulb@651 | 50 | |
paulb@651 | 51 | The success template must provide a document structure where the location of |
paulb@651 | 52 | the application can be found through this class's 'path_to_success_element' |
paulb@651 | 53 | attribute (which can be overridden or modified). Such a structure would be |
paulb@651 | 54 | as follows for the default configuration: |
paulb@651 | 55 | |
paulb@651 | 56 | <success mode="..." return_to="..." claimed_id="..." identity="..."/> |
paulb@651 | 57 | """ |
paulb@651 | 58 | |
paulb@651 | 59 | path_to_initiation_element = "/initiation" |
paulb@651 | 60 | path_to_success_element = "/success" |
paulb@651 | 61 | |
paulb@651 | 62 | def __init__(self, openid_mode=None, use_redirect=1): |
paulb@651 | 63 | |
paulb@651 | 64 | """ |
paulb@651 | 65 | Initialise the resource. |
paulb@651 | 66 | |
paulb@651 | 67 | The optional 'openid_mode' parameter may be set to "checkid_immediate" |
paulb@651 | 68 | or "checkid_setup" (the default). |
paulb@651 | 69 | |
paulb@651 | 70 | If the optional 'use_redirect' flag is set to a false value (which is |
paulb@651 | 71 | not the default), a confirmation screen is given instead of immediately |
paulb@651 | 72 | redirecting the user to the OpenID provider. |
paulb@651 | 73 | |
paulb@651 | 74 | To get the root of the application, this resource needs an attribute on |
paulb@651 | 75 | the transaction called "root". |
paulb@651 | 76 | """ |
paulb@651 | 77 | |
paulb@651 | 78 | OpenIDInitiationUtils.__init__(self, openid_mode, use_redirect) |
paulb@651 | 79 | |
paulb@651 | 80 | def select_activity(self, trans, form): |
paulb@651 | 81 | form.set_activity("initiation") |
paulb@651 | 82 | |
paulb@651 | 83 | def respond_to_input(self, trans, form): |
paulb@651 | 84 | parameters = form.get_parameters() |
paulb@651 | 85 | |
paulb@651 | 86 | if parameters.has_key("app"): |
paulb@651 | 87 | app = parameters["app"] |
paulb@651 | 88 | else: |
paulb@651 | 89 | app = trans.get_fields_from_path().get("app", [""])[0] |
paulb@651 | 90 | |
paulb@651 | 91 | if parameters.has_key("initiate"): |
paulb@651 | 92 | self.check_identity(trans, form, app) |
paulb@651 | 93 | # The above method does not return. |
paulb@651 | 94 | |
paulb@651 | 95 | # Otherwise, show the initiation form. |
paulb@651 | 96 | |
paulb@651 | 97 | self.show_initiation(trans, form, app) |
paulb@651 | 98 | |
paulb@651 | 99 | # Methods called by the OpenID logic. |
paulb@651 | 100 | |
paulb@651 | 101 | def check_identity(self, trans, form, app): |
paulb@651 | 102 | |
paulb@651 | 103 | """ |
paulb@651 | 104 | Check the identity found through 'trans' and 'fields', using 'app' and |
paulb@651 | 105 | discovered information about the identity to redirect to the provider. |
paulb@651 | 106 | """ |
paulb@651 | 107 | |
paulb@651 | 108 | doc = form.get_document() |
paulb@651 | 109 | parameters = form.get_parameters() |
paulb@651 | 110 | |
paulb@651 | 111 | initelem = doc.xpath(self.path_to_initiation_element)[0] |
paulb@651 | 112 | identity = initelem.getAttribute("identity") or parameters.get("identity", [""])[0] |
paulb@651 | 113 | claimed_identifier, provider, local_identifier = self.get_provider_url(trans, identity) |
paulb@651 | 114 | |
paulb@651 | 115 | if provider is not None: |
paulb@651 | 116 | self.redirect_to_provider(trans, form, app, claimed_identifier, provider, local_identifier) |
paulb@651 | 117 | |
paulb@651 | 118 | def redirect_to_provider(self, trans, form, app, claimed_identifier, provider, local_identifier): |
paulb@651 | 119 | |
paulb@651 | 120 | """ |
paulb@651 | 121 | Redirect the client using 'trans', 'form' and the given 'app', |
paulb@651 | 122 | 'claimed_identifier', 'provider' and 'local_identifier' details. |
paulb@651 | 123 | |
paulb@651 | 124 | See: |
paulb@651 | 125 | http://openid.net/specs/openid-authentication-2_0-12.html#rfc.section.5.2 |
paulb@651 | 126 | http://openid.net/specs/openid-authentication-2_0-12.html#rfc.section.9 |
paulb@651 | 127 | """ |
paulb@651 | 128 | |
paulb@651 | 129 | url = self.get_redirect_url(trans, app, claimed_identifier, provider, local_identifier) |
paulb@651 | 130 | |
paulb@651 | 131 | # Show the success page anyway. |
paulb@651 | 132 | # Offer a POST-based form for redirection. |
paulb@651 | 133 | |
paulb@651 | 134 | self.show_success(trans, form, provider, app, claimed_identifier, local_identifier) |
paulb@651 | 135 | |
paulb@651 | 136 | # Redirect to the OpenID provider URL. |
paulb@651 | 137 | |
paulb@651 | 138 | if self.use_redirect: |
paulb@651 | 139 | trans.redirect(url) |
paulb@651 | 140 | else: |
paulb@651 | 141 | raise WebStack.Generic.EndOfResponse |
paulb@651 | 142 | |
paulb@651 | 143 | def show_initiation(self, trans, form, app): |
paulb@651 | 144 | |
paulb@651 | 145 | """ |
paulb@651 | 146 | Writes a initiation screen using the transaction 'trans' and 'form', |
paulb@651 | 147 | including details of the 'app' which the client was attempting to |
paulb@651 | 148 | access. |
paulb@651 | 149 | """ |
paulb@651 | 150 | |
paulb@651 | 151 | doc = form.get_document() |
paulb@651 | 152 | parameters = form.get_parameters() |
paulb@651 | 153 | |
paulb@651 | 154 | initelem = doc.xpath(self.path_to_initiation_element)[0] |
paulb@651 | 155 | identity = initelem.getAttribute("identity") or parameters.get("identity", [""])[0] |
paulb@651 | 156 | app = initelem.getAttribute("app") or parameters.get("app", [""])[0] |
paulb@651 | 157 | |
paulb@651 | 158 | initelem = doc.xpath(self.path_to_initiation_element)[0] |
paulb@651 | 159 | initelem.setAttribute("identity", identity) |
paulb@651 | 160 | initelem.setAttribute("app", app) |
paulb@651 | 161 | |
paulb@651 | 162 | def show_success(self, trans, form, provider, app, claimed_identifier, local_identifier): |
paulb@651 | 163 | |
paulb@651 | 164 | """ |
paulb@651 | 165 | Writes a success screen using the transaction 'trans' and 'form', |
paulb@651 | 166 | including details of the OpenID 'provider', the 'app' URL, |
paulb@651 | 167 | 'claimed_identifier' and 'local_identifier'. |
paulb@651 | 168 | """ |
paulb@651 | 169 | |
paulb@651 | 170 | # Switch to the success activity. |
paulb@651 | 171 | |
paulb@651 | 172 | form.set_activity("success") |
paulb@651 | 173 | doc = form.new_instance("success") |
paulb@651 | 174 | successelem = doc.xpath(self.path_to_success_element)[0] |
paulb@651 | 175 | successelem.setAttribute("provider", provider) |
paulb@651 | 176 | successelem.setAttribute("ns", self.openid_ns) |
paulb@651 | 177 | successelem.setAttribute("mode", self.openid_mode) |
paulb@651 | 178 | successelem.setAttribute("return_to", app) |
paulb@651 | 179 | successelem.setAttribute("claimed_id", claimed_identifier) |
paulb@651 | 180 | successelem.setAttribute("identity", local_identifier) |
paulb@651 | 181 | |
paulb@651 | 182 | form.set_document(doc) |
paulb@651 | 183 | |
paulb@651 | 184 | # Output preparation. |
paulb@651 | 185 | |
paulb@651 | 186 | def create_output(self, trans, form): |
paulb@651 | 187 | attributes = trans.get_attributes() |
paulb@651 | 188 | |
paulb@651 | 189 | stylesheet_parameters = {} |
paulb@651 | 190 | references = {} |
paulb@651 | 191 | |
paulb@651 | 192 | # Set up translations. |
paulb@651 | 193 | |
paulb@651 | 194 | if self.document_resources.has_key("translations"): |
paulb@651 | 195 | translations_xml = self.prepare_document("translations") |
paulb@651 | 196 | |
paulb@651 | 197 | try: |
paulb@651 | 198 | language = trans.get_content_languages()[0] |
paulb@651 | 199 | except IndexError: |
paulb@651 | 200 | language = "en" |
paulb@651 | 201 | |
paulb@651 | 202 | stylesheet_parameters["locale"] = language |
paulb@651 | 203 | references["translations"] = translations_xml |
paulb@651 | 204 | |
paulb@651 | 205 | # Complete the response. |
paulb@651 | 206 | |
paulb@651 | 207 | stylesheet_parameters["root"] = attributes["root"] |
paulb@651 | 208 | XSLFormsResource.create_output(self, trans, form, stylesheet_parameters=stylesheet_parameters, references=references) |
paulb@651 | 209 | |
paulb@651 | 210 | # vim: tabstop=4 expandtab shiftwidth=4 |