~django-openid-auth/django-openid-auth/trunk

41 by James Henstridge
Slap BSD license headers on source code. Attribute Simon Willison for
1
# django-openid-auth -  OpenID integration for django.contrib.auth
2
#
3
# Copyright (C) 2007 Simon Willison
93 by Anthony Lenton
[r=elachuni,facundobatista] LP#936153, After login, redirecting to a URL that contains non-ASCII characters would fail because the naive "str(foo)" scheme used in urllib would use the default system encoding, which we can't trust at all.
4
# Copyright (C) 2008-2013 Canonical Ltd.
41 by James Henstridge
Slap BSD license headers on source code. Attribute Simon Willison for
5
#
6
# Redistribution and use in source and binary forms, with or without
7
# modification, are permitted provided that the following conditions
8
# are met:
9
#
10
# * Redistributions of source code must retain the above copyright
11
# notice, this list of conditions and the following disclaimer.
12
#
13
# * Redistributions in binary form must reproduce the above copyright
14
# notice, this list of conditions and the following disclaimer in the
15
# documentation and/or other materials provided with the distribution.
16
#
17
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
20
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
21
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
22
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
23
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
25
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
27
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28
# POSSIBILITY OF SUCH DAMAGE.
29
112.1.2 by Natalia
Ensure that we handle unicode everywhere.
30
from __future__ import unicode_literals
31
15 by James Henstridge
Rework the OpenID consumer code to be a bit clearer, and make it
32
import re
33
import urllib
59.1.1 by stuart.langridge at canonical
Allow redirect URL to be external if it's on the permitted list of external domains, defined in settings
34
from urlparse import urlsplit
15 by James Henstridge
Rework the OpenID consumer code to be a bit clearer, and make it
35
36
from django.conf import settings
19 by James Henstridge
Convert the auth.py code into a django.contrib.auth authentication
37
from django.contrib.auth import (
38
    REDIRECT_FIELD_NAME, authenticate, login as auth_login)
56.1.1 by Thomas Bechtold
add possibility to map automatic all django-groups to launchpad teams
39
from django.contrib.auth.models import Group
15 by James Henstridge
Rework the OpenID consumer code to be a bit clearer, and make it
40
from django.core.urlresolvers import reverse
41
from django.http import HttpResponse, HttpResponseRedirect
42
from django.shortcuts import render_to_response
2 by swillison
Initial checkin
43
from django.template import RequestContext
45 by James Henstridge
Clean up the failure handling a little, generating 403 responses rather
44
from django.template.loader import render_to_string
75.1.1 by Anthony Lenton
Added a try/except block when importing csrf decorator.
45
try:
46
    from django.views.decorators.csrf import csrf_exempt
47
except ImportError:
48
    from django.contrib.csrf.middleware import csrf_exempt
2 by swillison
Initial checkin
49
15 by James Henstridge
Rework the OpenID consumer code to be a bit clearer, and make it
50
from openid.consumer.consumer import (
51
    Consumer, SUCCESS, CANCEL, FAILURE)
2 by swillison
Initial checkin
52
from openid.consumer.discover import DiscoveryFailure
80.1.3 by Michael Hall
Finish test cases, add implementation
53
from openid.extensions import sreg, ax, pape
27.1.4 by john.lenton at canonical
made teams optional
54
30 by James Henstridge
Add teams extension to tree, rather than requiring patched
55
from django_openid_auth import teams
18 by James Henstridge
Rename package to django_openid_auth, since it differs from the
56
from django_openid_auth.forms import OpenIDLoginForm
76.2.1 by Michael Nelson
Adds a signal on successful login that includes the request and the sreg_response.
57
from django_openid_auth.models import UserOpenID
78.1.1 by Michael Nelson
Correct signal name and provide openid_response as kwarg instead of sreg_response.
58
from django_openid_auth.signals import openid_login_complete
18 by James Henstridge
Rename package to django_openid_auth, since it differs from the
59
from django_openid_auth.store import DjangoOpenIDStore
80.2.4 by Michael Hall
Merge from trunk
60
from django_openid_auth.exceptions import (
61
    DjangoOpenIDException,
62
)
15 by James Henstridge
Rework the OpenID consumer code to be a bit clearer, and make it
63
2 by swillison
Initial checkin
64
3 by swillison
Ready for launch
65
next_url_re = re.compile('^/[-\w/]+$')
66
112.1.1 by Natalia
Fully test suite passing on djangos from 1.4 to 1.8.
67
3 by swillison
Ready for launch
68
def is_valid_next_url(next):
2 by swillison
Initial checkin
69
    # When we allow this:
3 by swillison
Ready for launch
70
    #   /openid/?next=/welcome/
76.1.1 by Michael Nelson
Add an optional OPENID_SREG_EXTRA_FIELDS setting.
71
    # For security reasons we want to restrict the next= bit to being a local
2 by swillison
Initial checkin
72
    # path, not a complete URL.
3 by swillison
Ready for launch
73
    return bool(next_url_re.match(next))
2 by swillison
Initial checkin
74
15 by James Henstridge
Rework the OpenID consumer code to be a bit clearer, and make it
75
76
def sanitise_redirect_url(redirect_to):
77
    """Sanitise the redirection URL."""
78
    # Light security check -- make sure redirect_to isn't garbage.
63.1.1 by James Henstridge
Fix the sanitise_redirect_url function to handle an empty string properly.
79
    is_valid = True
80
    if not redirect_to or ' ' in redirect_to:
81
        is_valid = False
82
    elif '//' in redirect_to:
59.1.1 by stuart.langridge at canonical
Allow redirect URL to be external if it's on the permitted list of external domains, defined in settings
83
        # Allow the redirect URL to be external if it's a permitted domain
112.1.1 by Natalia
Fully test suite passing on djangos from 1.4 to 1.8.
84
        allowed_domains = getattr(
85
            settings, "ALLOWED_EXTERNAL_OPENID_REDIRECT_DOMAINS", [])
59.1.1 by stuart.langridge at canonical
Allow redirect URL to be external if it's on the permitted list of external domains, defined in settings
86
        s, netloc, p, q, f = urlsplit(redirect_to)
87
        # allow it if netloc is blank or if the domain is allowed
88
        if netloc:
89
            # a domain was specified. Is it an allowed domain?
90
            if netloc.find(":") != -1:
91
                netloc, _ = netloc.split(":", 1)
92
            if netloc not in allowed_domains:
63.1.1 by James Henstridge
Fix the sanitise_redirect_url function to handle an empty string properly.
93
                is_valid = False
94
95
    # If the return_to URL is not valid, use the default.
96
    if not is_valid:
97
        redirect_to = settings.LOGIN_REDIRECT_URL
98
15 by James Henstridge
Rework the OpenID consumer code to be a bit clearer, and make it
99
    return redirect_to
100
101
102
def make_consumer(request):
103
    """Create an OpenID Consumer object for the given Django request."""
104
    # Give the OpenID library its own space in the session object.
105
    session = request.session.setdefault('OPENID', {})
106
    store = DjangoOpenIDStore()
107
    return Consumer(session, store)
108
109
25 by James Henstridge
Add a test for the SSO login code path.
110
def render_openid_request(request, openid_request, return_to, trust_root=None):
15 by James Henstridge
Rework the OpenID consumer code to be a bit clearer, and make it
111
    """Render an OpenID authentication request."""
112
    if trust_root is None:
113
        trust_root = getattr(settings, 'OPENID_TRUST_ROOT',
114
                             request.build_absolute_uri('/'))
115
116
    if openid_request.shouldSendRedirect():
117
        redirect_url = openid_request.redirectURL(
118
            trust_root, return_to)
112.1.1 by Natalia
Fully test suite passing on djangos from 1.4 to 1.8.
119
        response = HttpResponseRedirect(redirect_url)
15 by James Henstridge
Rework the OpenID consumer code to be a bit clearer, and make it
120
    else:
121
        form_html = openid_request.htmlMarkup(
122
            trust_root, return_to, form_tag_attrs={'id': 'openid_message'})
112.1.1 by Natalia
Fully test suite passing on djangos from 1.4 to 1.8.
123
        response = HttpResponse(
124
            form_html, content_type='text/html;charset=UTF-8')
125
    return response
15 by James Henstridge
Rework the OpenID consumer code to be a bit clearer, and make it
126
127
66.1.4 by Richard Marko
Add posibility to change location of failure template.
128
def default_render_failure(request, message, status=403,
80.2.1 by Michael Hall
Let exceptions bubble up and pass them on to render_failure so we can give users helpful information when their login fails
129
                           template_name='openid/failure.html',
130
                           exception=None):
45 by James Henstridge
Clean up the failure handling a little, generating 403 responses rather
131
    """Render an error page to the user."""
132
    data = render_to_string(
80.2.1 by Michael Hall
Let exceptions bubble up and pass them on to render_failure so we can give users helpful information when their login fails
133
        template_name, dict(message=message, exception=exception),
45 by James Henstridge
Clean up the failure handling a little, generating 403 responses rather
134
        context_instance=RequestContext(request))
135
    return HttpResponse(data, status=status)
136
137
15 by James Henstridge
Rework the OpenID consumer code to be a bit clearer, and make it
138
def parse_openid_response(request):
139
    """Parse an OpenID response from a Django request."""
140
    # Short cut if there is no request parameters.
112.1.1 by Natalia
Fully test suite passing on djangos from 1.4 to 1.8.
141
    # if len(request.REQUEST) == 0:
15 by James Henstridge
Rework the OpenID consumer code to be a bit clearer, and make it
142
    #    return None
143
144
    current_url = request.build_absolute_uri()
145
146
    consumer = make_consumer(request)
147
    return consumer.complete(dict(request.REQUEST.items()), current_url)
148
149
150
def login_begin(request, template_name='openid/login.html',
72 by James Henstridge
(Richard Marko) Add hooks for customising the view behaviour.
151
                login_complete_view='openid-complete',
152
                form_class=OpenIDLoginForm,
66.1.4 by Richard Marko
Add posibility to change location of failure template.
153
                render_failure=default_render_failure,
15 by James Henstridge
Rework the OpenID consumer code to be a bit clearer, and make it
154
                redirect_field_name=REDIRECT_FIELD_NAME):
155
    """Begin an OpenID login request, possibly asking for an identity URL."""
156
    redirect_to = request.REQUEST.get(redirect_field_name, '')
157
158
    # Get the OpenID URL to try.  First see if we've been configured
159
    # to use a fixed server URL.
160
    openid_url = getattr(settings, 'OPENID_SSO_SERVER_URL', None)
161
162
    if openid_url is None:
163
        if request.POST:
72 by James Henstridge
(Richard Marko) Add hooks for customising the view behaviour.
164
            login_form = form_class(data=request.POST)
15 by James Henstridge
Rework the OpenID consumer code to be a bit clearer, and make it
165
            if login_form.is_valid():
26 by James Henstridge
Add more tests for the other login modes.
166
                openid_url = login_form.cleaned_data['openid_identifier']
15 by James Henstridge
Rework the OpenID consumer code to be a bit clearer, and make it
167
        else:
72 by James Henstridge
(Richard Marko) Add hooks for customising the view behaviour.
168
            login_form = form_class()
15 by James Henstridge
Rework the OpenID consumer code to be a bit clearer, and make it
169
170
        # Invalid or no form data:
171
        if openid_url is None:
112.1.1 by Natalia
Fully test suite passing on djangos from 1.4 to 1.8.
172
            context = {'form': login_form, redirect_field_name: redirect_to}
173
            return render_to_response(
174
                template_name, context,
175
                context_instance=RequestContext(request))
15 by James Henstridge
Rework the OpenID consumer code to be a bit clearer, and make it
176
177
    consumer = make_consumer(request)
178
    try:
179
        openid_request = consumer.begin(openid_url)
112.1.1 by Natalia
Fully test suite passing on djangos from 1.4 to 1.8.
180
    except DiscoveryFailure as exc:
45 by James Henstridge
Clean up the failure handling a little, generating 403 responses rather
181
        return render_failure(
80.2.1 by Michael Hall
Let exceptions bubble up and pass them on to render_failure so we can give users helpful information when their login fails
182
            request, "OpenID discovery error: %s" % (str(exc),), status=500,
183
            exception=exc)
15 by James Henstridge
Rework the OpenID consumer code to be a bit clearer, and make it
184
65.1.1 by James Henstridge
Add support for attribute exchange as a way to query user details.
185
    # Request some user details.  If the provider advertises support
186
    # for attribute exchange, use that.
99.2.20 by Ricardo Kirkner
only request account_verified if there are valid schemes registered for the endpoint
187
    endpoint = openid_request.endpoint
188
    if endpoint.supportsType(ax.AXMessage.ns_uri):
65.1.1 by James Henstridge
Add support for attribute exchange as a way to query user details.
189
        fetch_request = ax.FetchRequest()
190
        # We mark all the attributes as required, since Google ignores
191
        # optional attributes.  We request both the full name and
192
        # first/last components since some providers offer one but not
193
        # the other.
194
        for (attr, alias) in [
99.2.1 by James Tait
Request account verification in the AX request.
195
                ('http://axschema.org/contact/email', 'email'),
196
                ('http://axschema.org/namePerson', 'fullname'),
197
                ('http://axschema.org/namePerson/first', 'firstname'),
198
                ('http://axschema.org/namePerson/last', 'lastname'),
199
                ('http://axschema.org/namePerson/friendly', 'nickname'),
200
                # The myOpenID provider advertises AX support, but uses
201
                # attribute names from an obsolete draft of the
202
                # specification.  We request them for compatibility.
203
                ('http://schema.openid.net/contact/email', 'old_email'),
204
                ('http://schema.openid.net/namePerson', 'old_fullname'),
205
                ('http://schema.openid.net/namePerson/friendly',
99.2.20 by Ricardo Kirkner
only request account_verified if there are valid schemes registered for the endpoint
206
                 'old_nickname')]:
65.1.1 by James Henstridge
Add support for attribute exchange as a way to query user details.
207
            fetch_request.add(ax.AttrInfo(attr, alias=alias, required=True))
99.2.20 by Ricardo Kirkner
only request account_verified if there are valid schemes registered for the endpoint
208
209
        # conditionally require account_verified attribute
210
        verification_scheme_map = getattr(
211
            settings, 'OPENID_VALID_VERIFICATION_SCHEMES', {})
212
        valid_schemes = verification_scheme_map.get(
213
            endpoint.server_url, verification_scheme_map.get(None, ()))
214
        if valid_schemes:
215
            # there are valid schemes configured for this endpoint, so
216
            # request account_verified status
217
            fetch_request.add(ax.AttrInfo(
218
                'http://ns.login.ubuntu.com/2013/validation/account',
219
                alias='account_verified', required=True))
220
65.1.1 by James Henstridge
Add support for attribute exchange as a way to query user details.
221
        openid_request.addExtension(fetch_request)
222
    else:
82.1.1 by Michael Nelson
RED: Required fields should be required in SRegRequest.
223
        sreg_required_fields = []
224
        sreg_required_fields.extend(
225
            getattr(settings, 'OPENID_SREG_REQUIRED_FIELDS', []))
76.1.1 by Michael Nelson
Add an optional OPENID_SREG_EXTRA_FIELDS setting.
226
        sreg_optional_fields = ['email', 'fullname', 'nickname']
82.1.2 by Michael Nelson
GREEN: Enable required fields to be included in request.
227
        sreg_optional_fields.extend(
228
            getattr(settings, 'OPENID_SREG_EXTRA_FIELDS', []))
229
        sreg_optional_fields = [
112.1.1 by Natalia
Fully test suite passing on djangos from 1.4 to 1.8.
230
            field for field in sreg_optional_fields
231
            if field not in sreg_required_fields]
65.1.1 by James Henstridge
Add support for attribute exchange as a way to query user details.
232
        openid_request.addExtension(
82.1.1 by Michael Nelson
RED: Required fields should be required in SRegRequest.
233
            sreg.SRegRequest(optional=sreg_optional_fields,
112.1.1 by Natalia
Fully test suite passing on djangos from 1.4 to 1.8.
234
                             required=sreg_required_fields))
99.2.20 by Ricardo Kirkner
only request account_verified if there are valid schemes registered for the endpoint
235
80.1.3 by Michael Hall
Finish test cases, add implementation
236
    if getattr(settings, 'OPENID_PHYSICAL_MULTIFACTOR_REQUIRED', False):
237
        preferred_auth = [
238
            pape.AUTH_MULTI_FACTOR_PHYSICAL,
239
        ]
240
        pape_request = pape.Request(preferred_auth_policies=preferred_auth)
241
        openid_request.addExtension(pape_request)
242
30 by James Henstridge
Add teams extension to tree, rather than requiring patched
243
    # Request team info
112.1.1 by Natalia
Fully test suite passing on djangos from 1.4 to 1.8.
244
    teams_mapping_auto = getattr(
245
        settings, 'OPENID_LAUNCHPAD_TEAMS_MAPPING_AUTO', False)
246
    teams_mapping_auto_blacklist = getattr(
247
        settings, 'OPENID_LAUNCHPAD_TEAMS_MAPPING_AUTO_BLACKLIST', [])
32 by James Henstridge
Only add the teams extension to the request if we have teams to check.
248
    launchpad_teams = getattr(settings, 'OPENID_LAUNCHPAD_TEAMS_MAPPING', {})
56.1.1 by Thomas Bechtold
add possibility to map automatic all django-groups to launchpad teams
249
    if teams_mapping_auto:
112.1.1 by Natalia
Fully test suite passing on djangos from 1.4 to 1.8.
250
        # ignore launchpad teams. use all django-groups
56.1.1 by Thomas Bechtold
add possibility to map automatic all django-groups to launchpad teams
251
        launchpad_teams = dict()
112.1.1 by Natalia
Fully test suite passing on djangos from 1.4 to 1.8.
252
        all_groups = Group.objects.exclude(
253
            name__in=teams_mapping_auto_blacklist)
56.1.1 by Thomas Bechtold
add possibility to map automatic all django-groups to launchpad teams
254
        for group in all_groups:
255
            launchpad_teams[group.name] = group.name
256
32 by James Henstridge
Only add the teams extension to the request if we have teams to check.
257
    if launchpad_teams:
258
        openid_request.addExtension(teams.TeamsRequest(launchpad_teams.keys()))
27.1.1 by john.lenton at canonical
got it working! woohoo!
259
15 by James Henstridge
Rework the OpenID consumer code to be a bit clearer, and make it
260
    # Construct the request completion URL, including the page we
261
    # should redirect to.
72 by James Henstridge
(Richard Marko) Add hooks for customising the view behaviour.
262
    return_to = request.build_absolute_uri(reverse(login_complete_view))
15 by James Henstridge
Rework the OpenID consumer code to be a bit clearer, and make it
263
    if redirect_to:
264
        if '?' in return_to:
265
            return_to += '&'
266
        else:
267
            return_to += '?'
93 by Anthony Lenton
[r=elachuni,facundobatista] LP#936153, After login, redirecting to a URL that contains non-ASCII characters would fail because the naive "str(foo)" scheme used in urllib would use the default system encoding, which we can't trust at all.
268
        # Django gives us Unicode, which is great.  We must encode URI.
269
        # urllib enforces str. We can't trust anything about the default
89.2.1 by Chad MILLER
LP#936153, After login, redirecting to a URL that contains non-ASCII characters would fail because the naive "str(foo)" scheme used in urllib would use the default system encoding, which we can't trust at all.
270
        # encoding inside  str(foo) , so we must explicitly make foo a str.
271
        return_to += urllib.urlencode(
93 by Anthony Lenton
[r=elachuni,facundobatista] LP#936153, After login, redirecting to a URL that contains non-ASCII characters would fail because the naive "str(foo)" scheme used in urllib would use the default system encoding, which we can't trust at all.
272
            {redirect_field_name: redirect_to.encode("UTF-8")})
15 by James Henstridge
Rework the OpenID consumer code to be a bit clearer, and make it
273
274
    return render_openid_request(request, openid_request, return_to)
275
276
68 by James Henstridge
Make the login_complete view csrf_exempt, since it can legitimately
277
@csrf_exempt
66.1.4 by Richard Marko
Add posibility to change location of failure template.
278
def login_complete(request, redirect_field_name=REDIRECT_FIELD_NAME,
80.2.2 by Michael Hall
Cleanup exception class heirarchy and imports, make overriding the login failure handler a settings option, add additional test cases to veryfiy that overriding the handler works and that the proper exceptions are being passed
279
                   render_failure=None):
15 by James Henstridge
Rework the OpenID consumer code to be a bit clearer, and make it
280
    redirect_to = request.REQUEST.get(redirect_field_name, '')
112.1.1 by Natalia
Fully test suite passing on djangos from 1.4 to 1.8.
281
    render_failure = (
282
        render_failure or getattr(settings, 'OPENID_RENDER_FAILURE', None) or
283
        default_render_failure)
15 by James Henstridge
Rework the OpenID consumer code to be a bit clearer, and make it
284
285
    openid_response = parse_openid_response(request)
286
    if not openid_response:
45 by James Henstridge
Clean up the failure handling a little, generating 403 responses rather
287
        return render_failure(
288
            request, 'This is an OpenID relying party endpoint.')
13 by James Henstridge
Some changes for compatibility with current Django and Python-OpenID.
289
2 by swillison
Initial checkin
290
    if openid_response.status == SUCCESS:
80.2.1 by Michael Hall
Let exceptions bubble up and pass them on to render_failure so we can give users helpful information when their login fails
291
        try:
292
            user = authenticate(openid_response=openid_response)
293
        except DjangoOpenIDException, e:
80.2.4 by Michael Hall
Merge from trunk
294
            return render_failure(request, e.message, exception=e)
99.2.20 by Ricardo Kirkner
only request account_verified if there are valid schemes registered for the endpoint
295
17 by James Henstridge
Add code to integrate the OpenID code with django.contrib.auth.
296
        if user is not None:
297
            if user.is_active:
298
                auth_login(request, user)
112.1.1 by Natalia
Fully test suite passing on djangos from 1.4 to 1.8.
299
                response = HttpResponseRedirect(
300
                    sanitise_redirect_url(redirect_to))
76.2.1 by Michael Nelson
Adds a signal on successful login that includes the request and the sreg_response.
301
302
                # Notify any listeners that we successfully logged in.
112.1.1 by Natalia
Fully test suite passing on djangos from 1.4 to 1.8.
303
                openid_login_complete.send(
304
                    sender=UserOpenID, request=request,
78.1.1 by Michael Nelson
Correct signal name and provide openid_response as kwarg instead of sreg_response.
305
                    openid_response=openid_response)
76.2.1 by Michael Nelson
Adds a signal on successful login that includes the request and the sreg_response.
306
307
                return response
17 by James Henstridge
Add code to integrate the OpenID code with django.contrib.auth.
308
            else:
45 by James Henstridge
Clean up the failure handling a little, generating 403 responses rather
309
                return render_failure(request, 'Disabled account')
17 by James Henstridge
Add code to integrate the OpenID code with django.contrib.auth.
310
        else:
45 by James Henstridge
Clean up the failure handling a little, generating 403 responses rather
311
            return render_failure(request, 'Unknown user')
15 by James Henstridge
Rework the OpenID consumer code to be a bit clearer, and make it
312
    elif openid_response.status == FAILURE:
45 by James Henstridge
Clean up the failure handling a little, generating 403 responses rather
313
        return render_failure(
314
            request, 'OpenID authentication failed: %s' %
315
            openid_response.message)
2 by swillison
Initial checkin
316
    elif openid_response.status == CANCEL:
45 by James Henstridge
Clean up the failure handling a little, generating 403 responses rather
317
        return render_failure(request, 'Authentication cancelled')
2 by swillison
Initial checkin
318
    else:
15 by James Henstridge
Rework the OpenID consumer code to be a bit clearer, and make it
319
        assert False, (
320
            "Unknown OpenID response type: %r" % openid_response.status)
321
3 by swillison
Ready for launch
322
10 by swillison
Extracted new logo() view
323
def logo(request):
324
    return HttpResponse(
325
        OPENID_LOGO_BASE_64.decode('base64'), mimetype='image/gif'
326
    )
327
3 by swillison
Ready for launch
328
# Logo from http://openid.net/login-bg.gif
329
# Embedded here for convenience; you should serve this as a static file
330
OPENID_LOGO_BASE_64 = """
331
R0lGODlhEAAQAMQAAO3t7eHh4srKyvz8/P5pDP9rENLS0v/28P/17tXV1dHEvPDw8M3Nzfn5+d3d
332
3f5jA97Syvnv6MfLzcfHx/1mCPx4Kc/S1Pf189C+tP+xgv/k1N3OxfHy9NLV1/39/f///yH5BAAA
333
AAAALAAAAAAQABAAAAVq4CeOZGme6KhlSDoexdO6H0IUR+otwUYRkMDCUwIYJhLFTyGZJACAwQcg
334
EAQ4kVuEE2AIGAOPQQAQwXCfS8KQGAwMjIYIUSi03B7iJ+AcnmclHg4TAh0QDzIpCw4WGBUZeikD
335
Fzk0lpcjIQA7
336
"""