From fb2e51d907d342a6b3ff07137630db87877fdbbf Mon Sep 17 00:00:00 2001 From: Dirk Uys Date: Thu, 6 Jul 2023 14:29:47 +0200 Subject: [PATCH] Add missing 3rd party dep to get local docker image running --- .gitmodules | 2 +- 3rdparty/django-openid-auth/.bzrignore | 5 + 3rdparty/django-openid-auth/LICENSE.txt | 23 + 3rdparty/django-openid-auth/MANIFEST.in | 8 + 3rdparty/django-openid-auth/Makefile | 10 + 3rdparty/django-openid-auth/README.txt | 127 ++++ 3rdparty/django-openid-auth/TODO.txt | 0 .../django_openid_auth.egg-info/PKG-INFO | 31 + .../django_openid_auth.egg-info/SOURCES.txt | 33 ++ .../dependency_links.txt | 1 + .../django_openid_auth.egg-info/top_level.txt | 1 + .../django_openid_auth/__init__.py | 29 + .../django_openid_auth/admin.py | 90 +++ .../django_openid_auth/auth.py | 237 ++++++++ .../django_openid_auth/forms.py | 87 +++ .../django_openid_auth/management/__init__.py | 27 + .../management/commands/__init__.py | 27 + .../management/commands/openid_cleanup.py | 39 ++ .../django_openid_auth/models.py | 72 +++ .../django_openid_auth/store.py | 131 +++++ .../django_openid_auth/teams.py | 411 +++++++++++++ .../templates/openid/failure.html | 11 + .../templates/openid/login.html | 43 ++ .../django_openid_auth/tests/__init__.py | 37 ++ .../django_openid_auth/tests/test_auth.py | 133 +++++ .../django_openid_auth/tests/test_store.py | 193 ++++++ .../django_openid_auth/tests/test_views.py | 549 ++++++++++++++++++ .../django_openid_auth/tests/urls.py | 39 ++ .../django_openid_auth/urls.py | 36 ++ .../django_openid_auth/views.py | 268 +++++++++ .../example_consumer/__init__.py | 28 + .../example_consumer/manage.py | 11 + .../example_consumer/settings.py | 133 +++++ .../example_consumer/urls.py | 45 ++ .../example_consumer/views.py | 57 ++ 3rdparty/django-openid-auth/openid.html | 150 +++++ 3rdparty/django-openid-auth/openid.txt | 165 ++++++ 3rdparty/django-openid-auth/setup.py | 80 +++ Dockerfile | 1 + README.rst | 5 + lernanta/requirements/prod.txt | 22 +- 41 files changed, 3385 insertions(+), 12 deletions(-) create mode 100644 3rdparty/django-openid-auth/.bzrignore create mode 100644 3rdparty/django-openid-auth/LICENSE.txt create mode 100644 3rdparty/django-openid-auth/MANIFEST.in create mode 100644 3rdparty/django-openid-auth/Makefile create mode 100644 3rdparty/django-openid-auth/README.txt create mode 100644 3rdparty/django-openid-auth/TODO.txt create mode 100644 3rdparty/django-openid-auth/django_openid_auth.egg-info/PKG-INFO create mode 100644 3rdparty/django-openid-auth/django_openid_auth.egg-info/SOURCES.txt create mode 100644 3rdparty/django-openid-auth/django_openid_auth.egg-info/dependency_links.txt create mode 100644 3rdparty/django-openid-auth/django_openid_auth.egg-info/top_level.txt create mode 100644 3rdparty/django-openid-auth/django_openid_auth/__init__.py create mode 100644 3rdparty/django-openid-auth/django_openid_auth/admin.py create mode 100644 3rdparty/django-openid-auth/django_openid_auth/auth.py create mode 100644 3rdparty/django-openid-auth/django_openid_auth/forms.py create mode 100644 3rdparty/django-openid-auth/django_openid_auth/management/__init__.py create mode 100644 3rdparty/django-openid-auth/django_openid_auth/management/commands/__init__.py create mode 100644 3rdparty/django-openid-auth/django_openid_auth/management/commands/openid_cleanup.py create mode 100644 3rdparty/django-openid-auth/django_openid_auth/models.py create mode 100644 3rdparty/django-openid-auth/django_openid_auth/store.py create mode 100644 3rdparty/django-openid-auth/django_openid_auth/teams.py create mode 100644 3rdparty/django-openid-auth/django_openid_auth/templates/openid/failure.html create mode 100644 3rdparty/django-openid-auth/django_openid_auth/templates/openid/login.html create mode 100644 3rdparty/django-openid-auth/django_openid_auth/tests/__init__.py create mode 100644 3rdparty/django-openid-auth/django_openid_auth/tests/test_auth.py create mode 100644 3rdparty/django-openid-auth/django_openid_auth/tests/test_store.py create mode 100644 3rdparty/django-openid-auth/django_openid_auth/tests/test_views.py create mode 100644 3rdparty/django-openid-auth/django_openid_auth/tests/urls.py create mode 100644 3rdparty/django-openid-auth/django_openid_auth/urls.py create mode 100644 3rdparty/django-openid-auth/django_openid_auth/views.py create mode 100644 3rdparty/django-openid-auth/example_consumer/__init__.py create mode 100755 3rdparty/django-openid-auth/example_consumer/manage.py create mode 100644 3rdparty/django-openid-auth/example_consumer/settings.py create mode 100644 3rdparty/django-openid-auth/example_consumer/urls.py create mode 100644 3rdparty/django-openid-auth/example_consumer/views.py create mode 100644 3rdparty/django-openid-auth/openid.html create mode 100644 3rdparty/django-openid-auth/openid.txt create mode 100644 3rdparty/django-openid-auth/setup.py diff --git a/.gitmodules b/.gitmodules index a333ee22..377ad307 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ [submodule "lernanta/static/sass/p2pu-css-framework"] path = lernanta/static/sass/p2pu-css-framework - url = http://github.com/p2pu/p2pu-css-framework + url = http://github.com/p2pu/p2pu-theme diff --git a/3rdparty/django-openid-auth/.bzrignore b/3rdparty/django-openid-auth/.bzrignore new file mode 100644 index 00000000..bc3b0fc2 --- /dev/null +++ b/3rdparty/django-openid-auth/.bzrignore @@ -0,0 +1,5 @@ +./django +./MANIFEST +./build +./dist +./sqlite.db diff --git a/3rdparty/django-openid-auth/LICENSE.txt b/3rdparty/django-openid-auth/LICENSE.txt new file mode 100644 index 00000000..3abea62f --- /dev/null +++ b/3rdparty/django-openid-auth/LICENSE.txt @@ -0,0 +1,23 @@ +Copyright (C) 2007 Simon Willison +Copyright (C) 2008-2010 Canonical Ltd. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this +list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/3rdparty/django-openid-auth/MANIFEST.in b/3rdparty/django-openid-auth/MANIFEST.in new file mode 100644 index 00000000..acc52f6b --- /dev/null +++ b/3rdparty/django-openid-auth/MANIFEST.in @@ -0,0 +1,8 @@ +include Makefile +include MANIFEST.in +include LICENSE.txt +include README.txt +include TODO.txt + +recursive-include django_openid_auth/templates *.html +recursive-include example_consumer *.py diff --git a/3rdparty/django-openid-auth/Makefile b/3rdparty/django-openid-auth/Makefile new file mode 100644 index 00000000..d43e6cbf --- /dev/null +++ b/3rdparty/django-openid-auth/Makefile @@ -0,0 +1,10 @@ + +check: + PYTHONPATH=$(shell pwd) python example_consumer/manage.py test \ + --verbosity=2 django_openid_auth + +run-example-consumer: + PYTHONPATH=$(shell pwd) python example_consumer/manage.py syncdb + PYTHONPATH=$(shell pwd) python example_consumer/manage.py runserver + +.PHONY: check run-example-consumer diff --git a/3rdparty/django-openid-auth/README.txt b/3rdparty/django-openid-auth/README.txt new file mode 100644 index 00000000..4fc27d11 --- /dev/null +++ b/3rdparty/django-openid-auth/README.txt @@ -0,0 +1,127 @@ += Django OpenID Authentication Support = + +This package provides integration between Django's authentication +system and OpenID authentication. It also includes support for using +a fixed OpenID server endpoint, which can be useful when implementing +single signon systems. + + +== Basic Installation == + + 1. Install the Jan Rain Python OpenID library. It can be found at: + + http://openidenabled.com/python-openid/ + + It can also be found in most Linux distributions packaged as + "python-openid". You will need version 2.2.0 or later. + + 2. Add 'django_openid_auth' to INSTALLED_APPS for your application. + At a minimum, you'll need the following in there: + + INSTALLED_APPS = ( + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django_openid_auth', + ) + + 3. Add 'django_auth_openid.auth.OpenIDBackend' to + AUTHENTICATION_BACKENDS. This should be in addition to the + default ModelBackend: + + AUTHENTICATION_BACKENDS = ( + 'django_openid_auth.auth.OpenIDBackend', + 'django.contrib.auth.backends.ModelBackend', + ) + + 4. To create users automatically when a new OpenID is used, add the + following to the settings: + + OPENID_CREATE_USERS = True + + 5. To have user details updated from OpenID Simple Registration or + Attribute Exchange extension data each time they log in, add the + following: + + OPENID_UPDATE_DETAILS_FROM_SREG = True + + 6. Hook up the login URLs to your application's urlconf with + something like: + + urlpatterns = patterns('', + ... + (r'^openid/', include('django_openid_auth.urls')), + ... + ) + + 7. Configure the LOGIN_URL and LOGIN_REDIRECT_URL appropriately for + your site: + + LOGIN_URL = '/openid/login/' + LOGIN_REDIRECT_URL = '/' + + This will allow pages that use the standard @login_required + decorator to use the OpenID login page. + + 8. Rerun "python manage.py syncdb" to add the UserOpenID table to + your database. + + +== Configuring Single Sign-On == + +If you only want to accept identities from a single OpenID server and +that server implemnts OpenID 2.0 identifier select mode, add the +following setting to your app: + + OPENID_SSO_SERVER_URL = 'server-endpoint-url' + +With this setting enabled, the user will not be prompted to enter +their identity URL, and instead an OpenID authentication request will +be started with the given server URL. + +As an example, to use Launchpad accounts for SSO, you'd use: + + OPENID_SSO_SERVER_URL = 'https://login.launchpad.net/' + + +== Launchpad Teams Support == + +This library supports the Launchpad Teams OpenID extension. Using +this feature, it is possible to map Launchpad team memberships to +Django group memberships. It can be configured with: + + OPENID_SSO_SERVER_URL = 'https://login.launchpad.net/' + OPENID_LAUNCHPAD_TEAMS_MAPPING = { + 'launchpad-team-1': 'django-group-1', + 'launchpad-team-2': 'django-group-2', + } + +When a user logs in, they will be added or removed from the relevant +teams listed in the mapping. + +If you have already django-groups and want to map these groups automatically, you can use the OPENID_LAUNCHPAD_TEAMS_MAPPING_AUTO variable in your settings.py file. + + OPENID_LAUNCHPAD_TEAMS_MAPPING_AUTO = True + +If you use OPENID_LAUNCHPAD_TEAMS_MAPPING_AUTO, the variable OPENID_LAUNCHPAD_TEAMS_MAPPING will be ignored. +If you want to exclude some groups from the auto mapping, use OPENID_LAUNCHPAD_TEAMS_MAPPING_AUTO_BLACKLIST. This variable has only an effect if OPENID_LAUNCHPAD_TEAMS_MAPPING_AUTO is True. + + OPENID_LAUNCHPAD_TEAMS_MAPPING_AUTO_BLACKLIST = ['django-group1', 'django-group2'] + +== External redirect domains == + +By default, redirecting back to an external URL after auth is forbidden. To permit redirection to external URLs on a separate domain, define ALLOWED_EXTERNAL_OPENID_REDIRECT_DOMAINS in your settings.py file as a list of permitted domains: + + ALLOWED_EXTERNAL_OPENID_REDIRECT_DOMAINS = ['example.com', 'example.org'] + +and redirects to external URLs on those domains will additionally be permitted. + +== Use as /admin (django.admin.contrib) login == + +If you require openid authentication into the admin application, add the following setting: + + OPENID_USE_AS_ADMIN_LOGIN = True + +It is worth noting that a user needs to be be marked as a "staff user" to be able to access the admin interface. A new openid user will not normally be a "staff user". +The easiest way to resolve this is to use traditional authentication (OPENID_USE_AS_ADMIN_LOGIN = False) to sign in as your first user with a password and authorise your +openid user to be staff. diff --git a/3rdparty/django-openid-auth/TODO.txt b/3rdparty/django-openid-auth/TODO.txt new file mode 100644 index 00000000..e69de29b diff --git a/3rdparty/django-openid-auth/django_openid_auth.egg-info/PKG-INFO b/3rdparty/django-openid-auth/django_openid_auth.egg-info/PKG-INFO new file mode 100644 index 00000000..a4a84c26 --- /dev/null +++ b/3rdparty/django-openid-auth/django_openid_auth.egg-info/PKG-INFO @@ -0,0 +1,31 @@ +Metadata-Version: 1.1 +Name: django-openid-auth +Version: 0.3 +Summary: OpenID integration for django.contrib.auth +Home-page: https://launchpad.net/django-openid-auth +Author: Canonical Ltd +Author-email: UNKNOWN +License: BSD +Download-URL: http://launchpad.net/django-openid-auth/trunk/0.3/+download/django-openid-auth-0.3.tar.gz +Description: A library that can be used to add OpenID support to Django applications. + The library integrates with Django's built in authentication system, so + most applications require minimal changes to support OpenID llogin. The + library also includes the following features: + * Basic user details are transferred from the OpenID server via the + Simple Registration extension or Attribute Exchange extension. + * can be configured to use a fixed OpenID server URL, for use in SSO. + * supports the launchpad.net teams extension to get team membership + info. + +Platform: any +Classifier: Development Status :: 4 - Beta +Classifier: Environment :: Web Environment +Classifier: Framework :: Django +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: BSD License +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Topic :: Software Development :: Libraries :: Python Modules +Requires: django (>=1.2) +Requires: openid (>=2.2.0) +Provides: django_openid_auth diff --git a/3rdparty/django-openid-auth/django_openid_auth.egg-info/SOURCES.txt b/3rdparty/django-openid-auth/django_openid_auth.egg-info/SOURCES.txt new file mode 100644 index 00000000..f06b5004 --- /dev/null +++ b/3rdparty/django-openid-auth/django_openid_auth.egg-info/SOURCES.txt @@ -0,0 +1,33 @@ +LICENSE.txt +MANIFEST.in +Makefile +README.txt +TODO.txt +django_openid_auth/__init__.py +django_openid_auth/admin.py +django_openid_auth/auth.py +django_openid_auth/forms.py +django_openid_auth/models.py +django_openid_auth/store.py +django_openid_auth/teams.py +django_openid_auth/urls.py +django_openid_auth/views.py +django_openid_auth.egg-info/PKG-INFO +django_openid_auth.egg-info/SOURCES.txt +django_openid_auth.egg-info/dependency_links.txt +django_openid_auth.egg-info/top_level.txt +django_openid_auth/management/__init__.py +django_openid_auth/management/commands/__init__.py +django_openid_auth/management/commands/openid_cleanup.py +django_openid_auth/templates/openid/failure.html +django_openid_auth/templates/openid/login.html +django_openid_auth/tests/__init__.py +django_openid_auth/tests/test_auth.py +django_openid_auth/tests/test_store.py +django_openid_auth/tests/test_views.py +django_openid_auth/tests/urls.py +example_consumer/__init__.py +example_consumer/manage.py +example_consumer/settings.py +example_consumer/urls.py +example_consumer/views.py \ No newline at end of file diff --git a/3rdparty/django-openid-auth/django_openid_auth.egg-info/dependency_links.txt b/3rdparty/django-openid-auth/django_openid_auth.egg-info/dependency_links.txt new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/3rdparty/django-openid-auth/django_openid_auth.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/3rdparty/django-openid-auth/django_openid_auth.egg-info/top_level.txt b/3rdparty/django-openid-auth/django_openid_auth.egg-info/top_level.txt new file mode 100644 index 00000000..16d0a727 --- /dev/null +++ b/3rdparty/django-openid-auth/django_openid_auth.egg-info/top_level.txt @@ -0,0 +1 @@ +django_openid_auth diff --git a/3rdparty/django-openid-auth/django_openid_auth/__init__.py b/3rdparty/django-openid-auth/django_openid_auth/__init__.py new file mode 100644 index 00000000..d44036b0 --- /dev/null +++ b/3rdparty/django-openid-auth/django_openid_auth/__init__.py @@ -0,0 +1,29 @@ +# django-openid-auth - OpenID integration for django.contrib.auth +# +# Copyright (C) 2007 Simon Willison +# Copyright (C) 2008-2010 Canonical Ltd. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + diff --git a/3rdparty/django-openid-auth/django_openid_auth/admin.py b/3rdparty/django-openid-auth/django_openid_auth/admin.py new file mode 100644 index 00000000..3540aa53 --- /dev/null +++ b/3rdparty/django-openid-auth/django_openid_auth/admin.py @@ -0,0 +1,90 @@ +# django-openid-auth - OpenID integration for django.contrib.auth +# +# Copyright (C) 2008-2010 Canonical Ltd. +# Copyright (C) 2010 Dave Walker +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +from django.conf import settings +from django.contrib import admin +from django_openid_auth.models import Nonce, Association, UserOpenID +from django_openid_auth.store import DjangoOpenIDStore + + +class NonceAdmin(admin.ModelAdmin): + list_display = ('server_url', 'timestamp') + actions = ['cleanup_nonces'] + + def cleanup_nonces(self, request, queryset): + store = DjangoOpenIDStore() + count = store.cleanupNonces() + self.message_user(request, "%d expired nonces removed" % count) + cleanup_nonces.short_description = "Clean up expired nonces" + +admin.site.register(Nonce, NonceAdmin) + + +class AssociationAdmin(admin.ModelAdmin): + list_display = ('server_url', 'assoc_type') + list_filter = ('assoc_type',) + search_fields = ('server_url',) + actions = ['cleanup_associations'] + + def cleanup_associations(self, request, queryset): + store = DjangoOpenIDStore() + count = store.cleanupAssociations() + self.message_user(request, "%d expired associations removed" % count) + cleanup_associations.short_description = "Clean up expired associations" + +admin.site.register(Association, AssociationAdmin) + + +class UserOpenIDAdmin(admin.ModelAdmin): + list_display = ('user', 'claimed_id') + search_fields = ('claimed_id',) + +admin.site.register(UserOpenID, UserOpenIDAdmin) + + +# Support for allowing openid authentication for /admin (django.contrib.admin) +if getattr(settings, 'OPENID_USE_AS_ADMIN_LOGIN', False): + from django.http import HttpResponseRedirect + from django_openid_auth import views + + def _openid_login(self, request, error_message='', extra_context=None): + if request.user.is_authenticated(): + if not request.user.is_staff: + return views.render_failure( + request, "User %s does not have admin access." + % request.user.username) + return views.render_failure( + request, "Unknown Error: %s" % error_message) + else: + # Redirect to openid login path, + return HttpResponseRedirect( + settings.LOGIN_URL + "?next=" + request.get_full_path()) + + # Overide the standard admin login form. + admin.sites.AdminSite.display_login_form = _openid_login diff --git a/3rdparty/django-openid-auth/django_openid_auth/auth.py b/3rdparty/django-openid-auth/django_openid_auth/auth.py new file mode 100644 index 00000000..962f22c8 --- /dev/null +++ b/3rdparty/django-openid-auth/django_openid_auth/auth.py @@ -0,0 +1,237 @@ +# django-openid-auth - OpenID integration for django.contrib.auth +# +# Copyright (C) 2008-2010 Canonical Ltd. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +"""Glue between OpenID and django.contrib.auth.""" + +__metaclass__ = type + +from django.conf import settings +from django.contrib.auth.models import User, Group +from openid.consumer.consumer import SUCCESS +from openid.extensions import ax, sreg + +from django_openid_auth import teams +from django_openid_auth.models import UserOpenID + + +class IdentityAlreadyClaimed(Exception): + pass + + +class OpenIDBackend: + """A django.contrib.auth backend that authenticates the user based on + an OpenID response.""" + + def get_user(self, user_id): + try: + return User.objects.get(pk=user_id) + except User.DoesNotExist: + return None + + def authenticate(self, **kwargs): + """Authenticate the user based on an OpenID response.""" + # Require that the OpenID response be passed in as a keyword + # argument, to make sure we don't match the username/password + # calling conventions of authenticate. + + openid_response = kwargs.get('openid_response') + if openid_response is None: + return None + + if openid_response.status != SUCCESS: + return None + + user = None + try: + user_openid = UserOpenID.objects.get( + claimed_id__exact=openid_response.identity_url) + except UserOpenID.DoesNotExist: + if getattr(settings, 'OPENID_CREATE_USERS', False): + user = self.create_user_from_openid(openid_response) + else: + user = user_openid.user + + if user is None: + return None + + if getattr(settings, 'OPENID_UPDATE_DETAILS_FROM_SREG', False): + details = self._extract_user_details(openid_response) + self.update_user_details(user, details) + + teams_response = teams.TeamsResponse.fromSuccessResponse( + openid_response) + if teams_response: + self.update_groups_from_teams(user, teams_response) + self.update_staff_status_from_teams(user, teams_response) + + return user + + def _extract_user_details(self, openid_response): + email = fullname = first_name = last_name = nickname = None + sreg_response = sreg.SRegResponse.fromSuccessResponse(openid_response) + if sreg_response: + email = sreg_response.get('email') + fullname = sreg_response.get('fullname') + nickname = sreg_response.get('nickname') + + # If any attributes are provided via Attribute Exchange, use + # them in preference. + fetch_response = ax.FetchResponse.fromSuccessResponse(openid_response) + if fetch_response: + # The myOpenID provider advertises AX support, but uses + # attribute names from an obsolete draft of the + # specification. We check for them first so the common + # names take precedence. + email = fetch_response.getSingle( + 'http://schema.openid.net/contact/email', email) + fullname = fetch_response.getSingle( + 'http://schema.openid.net/namePerson', fullname) + nickname = fetch_response.getSingle( + 'http://schema.openid.net/namePerson/friendly', nickname) + + email = fetch_response.getSingle( + 'http://axschema.org/contact/email', email) + fullname = fetch_response.getSingle( + 'http://axschema.org/namePerson', fullname) + first_name = fetch_response.getSingle( + 'http://axschema.org/namePerson/first', first_name) + last_name = fetch_response.getSingle( + 'http://axschema.org/namePerson/last', last_name) + nickname = fetch_response.getSingle( + 'http://axschema.org/namePerson/friendly', nickname) + + if fullname and not (first_name or last_name): + # Django wants to store first and last names separately, + # so we do our best to split the full name. + if ' ' in fullname: + first_name, last_name = fullname.rsplit(None, 1) + else: + first_name = u'' + last_name = fullname + + return dict(email=email, nickname=nickname, + first_name=first_name, last_name=last_name) + + def create_user_from_openid(self, openid_response): + details = self._extract_user_details(openid_response) + nickname = details['nickname'] or 'openiduser' + email = details['email'] or '' + + # Pick a username for the user based on their nickname, + # checking for conflicts. + i = 1 + while True: + username = nickname + if i > 1: + username += str(i) + try: + User.objects.get(username__exact=username) + except User.DoesNotExist: + break + i += 1 + + user = User.objects.create_user(username, email, password=None) + self.update_user_details(user, details) + + self.associate_openid(user, openid_response) + return user + + def associate_openid(self, user, openid_response): + """Associate an OpenID with a user account.""" + # Check to see if this OpenID has already been claimed. + try: + user_openid = UserOpenID.objects.get( + claimed_id__exact=openid_response.identity_url) + except UserOpenID.DoesNotExist: + user_openid = UserOpenID( + user=user, + claimed_id=openid_response.identity_url, + display_id=openid_response.endpoint.getDisplayIdentifier()) + user_openid.save() + else: + if user_openid.user != user: + raise IdentityAlreadyClaimed( + "The identity %s has already been claimed" + % openid_response.identity_url) + + return user_openid + + def update_user_details(self, user, details): + updated = False + if details['first_name']: + user.first_name = details['first_name'] + updated = True + if details['last_name']: + user.last_name = details['last_name'] + updated = True + if details['email']: + user.email = details['email'] + updated = True + + if updated: + user.save() + + def update_groups_from_teams(self, user, teams_response): + teams_mapping_auto = getattr(settings, 'OPENID_LAUNCHPAD_TEAMS_MAPPING_AUTO', False) + teams_mapping_auto_blacklist = getattr(settings, 'OPENID_LAUNCHPAD_TEAMS_MAPPING_AUTO_BLACKLIST', []) + teams_mapping = getattr(settings, 'OPENID_LAUNCHPAD_TEAMS_MAPPING', {}) + if teams_mapping_auto: + #ignore teams_mapping. use all django-groups + teams_mapping = dict() + all_groups = Group.objects.exclude(name__in=teams_mapping_auto_blacklist) + for group in all_groups: + teams_mapping[group.name] = group.name + + if len(teams_mapping) == 0: + return + + current_groups = set(user.groups.filter( + name__in=teams_mapping.values())) + desired_groups = set(Group.objects.filter( + name__in=[teams_mapping[lp_team] + for lp_team in teams_response.is_member + if lp_team in teams_mapping])) + for group in current_groups - desired_groups: + user.groups.remove(group) + for group in desired_groups - current_groups: + user.groups.add(group) + + def update_staff_status_from_teams(self, user, teams_response): + if not hasattr(settings, 'OPENID_LAUNCHPAD_STAFF_TEAMS'): + return + + staff_teams = getattr(settings, 'OPENID_LAUNCHPAD_STAFF_TEAMS', []) + user.is_staff = False + + for lp_team in teams_response.is_member: + if lp_team in staff_teams: + user.is_staff = True + break + + user.save() + diff --git a/3rdparty/django-openid-auth/django_openid_auth/forms.py b/3rdparty/django-openid-auth/django_openid_auth/forms.py new file mode 100644 index 00000000..58aa6ede --- /dev/null +++ b/3rdparty/django-openid-auth/django_openid_auth/forms.py @@ -0,0 +1,87 @@ +# django-openid-auth - OpenID integration for django.contrib.auth +# +# Copyright (C) 2007 Simon Willison +# Copyright (C) 2008-2010 Canonical Ltd. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +from django import forms +from django.contrib.auth.admin import UserAdmin +from django.contrib.auth.forms import UserChangeForm +from django.contrib.auth.models import Group +from django.utils.translation import ugettext as _ +from django.conf import settings + +from openid.yadis import xri + + +def teams_new_unicode(self): + """ + Replacement for Group.__unicode__() + Calls original method to chain results + """ + name = self.unicode_before_teams() + teams_mapping = getattr(settings, 'OPENID_LAUNCHPAD_TEAMS_MAPPING', {}) + group_teams = [t for t in teams_mapping if teams_mapping[t] == self.name] + if len(group_teams) > 0: + return "%s -> %s" % (name, ", ".join(group_teams)) + else: + return name +Group.unicode_before_teams = Group.__unicode__ +Group.__unicode__ = teams_new_unicode + + +class UserChangeFormWithTeamRestriction(UserChangeForm): + """ + Extends UserChangeForm to add teams awareness to the user admin form + """ + def clean_groups(self): + data = self.cleaned_data['groups'] + teams_mapping = getattr(settings, 'OPENID_LAUNCHPAD_TEAMS_MAPPING', {}) + known_teams = teams_mapping.values() + user_groups = self.instance.groups.all() + for group in data: + if group.name in known_teams and group not in user_groups: + raise forms.ValidationError("""The group %s is mapped to an + external team. You cannot assign it manually.""" % group.name) + return data +UserAdmin.form = UserChangeFormWithTeamRestriction + + +class OpenIDLoginForm(forms.Form): + openid_identifier = forms.CharField( + max_length=255, + widget=forms.TextInput(attrs={'class': 'required openid'})) + + def clean_openid_identifier(self): + if 'openid_identifier' in self.cleaned_data: + openid_identifier = self.cleaned_data['openid_identifier'] + if xri.identifierScheme(openid_identifier) == 'XRI' and getattr( + settings, 'OPENID_DISALLOW_INAMES', False + ): + raise forms.ValidationError(_('i-names are not supported')) + return self.cleaned_data['openid_identifier'] + + diff --git a/3rdparty/django-openid-auth/django_openid_auth/management/__init__.py b/3rdparty/django-openid-auth/django_openid_auth/management/__init__.py new file mode 100644 index 00000000..82a5e203 --- /dev/null +++ b/3rdparty/django-openid-auth/django_openid_auth/management/__init__.py @@ -0,0 +1,27 @@ +# django-openid-auth - OpenID integration for django.contrib.auth +# +# Copyright (C) 2009-2010 Canonical Ltd. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. diff --git a/3rdparty/django-openid-auth/django_openid_auth/management/commands/__init__.py b/3rdparty/django-openid-auth/django_openid_auth/management/commands/__init__.py new file mode 100644 index 00000000..82a5e203 --- /dev/null +++ b/3rdparty/django-openid-auth/django_openid_auth/management/commands/__init__.py @@ -0,0 +1,27 @@ +# django-openid-auth - OpenID integration for django.contrib.auth +# +# Copyright (C) 2009-2010 Canonical Ltd. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. diff --git a/3rdparty/django-openid-auth/django_openid_auth/management/commands/openid_cleanup.py b/3rdparty/django-openid-auth/django_openid_auth/management/commands/openid_cleanup.py new file mode 100644 index 00000000..ef2d85ab --- /dev/null +++ b/3rdparty/django-openid-auth/django_openid_auth/management/commands/openid_cleanup.py @@ -0,0 +1,39 @@ +# django-openid-auth - OpenID integration for django.contrib.auth +# +# Copyright (C) 2009-2010 Canonical Ltd. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +from django.core.management.base import NoArgsCommand + +from django_openid_auth.store import DjangoOpenIDStore + + +class Command(NoArgsCommand): + help = 'Clean up stale OpenID associations and nonces' + + def handle_noargs(self, **options): + store = DjangoOpenIDStore() + store.cleanup() diff --git a/3rdparty/django-openid-auth/django_openid_auth/models.py b/3rdparty/django-openid-auth/django_openid_auth/models.py new file mode 100644 index 00000000..193eb614 --- /dev/null +++ b/3rdparty/django-openid-auth/django_openid_auth/models.py @@ -0,0 +1,72 @@ +# django-openid-auth - OpenID integration for django.contrib.auth +# +# Copyright (C) 2007 Simon Willison +# Copyright (C) 2008-2010 Canonical Ltd. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +from django.core.exceptions import ValidationError +from django.contrib.auth.models import User +from django.db import models + + +class Nonce(models.Model): + server_url = models.CharField(max_length=2047) + timestamp = models.IntegerField() + salt = models.CharField(max_length=40) + + def __unicode__(self): + return u"Nonce: %s, %s" % (self.server_url, self.salt) + + +class Association(models.Model): + server_url = models.TextField(max_length=2047) + handle = models.CharField(max_length=255) + secret = models.TextField(max_length=255) # Stored base64 encoded + issued = models.IntegerField() + lifetime = models.IntegerField() + assoc_type = models.TextField(max_length=64) + + def __unicode__(self): + return u"Association: %s, %s" % (self.server_url, self.handle) + + +class UserOpenID(models.Model): + user = models.ForeignKey(User) + claimed_id = models.TextField(max_length=2047) + display_id = models.TextField(max_length=2047) + + def clean_fields(self): + """ + Validate uniqueness of claimed_id here because MySQL + doesn't like using unique on TextFields without specifying + a key length, which Django doesn't allow you to do. This can + be removed if bug # 524796 [1] get's fixed. + + [1] https://bugs.launchpad.net/django-openid-auth/+bug/524796 + """ + claims = self.objects.filter(claimed_id=self.claimed_id) + if claims: + raise ValidationError('Claimed ID must be unique') diff --git a/3rdparty/django-openid-auth/django_openid_auth/store.py b/3rdparty/django-openid-auth/django_openid_auth/store.py new file mode 100644 index 00000000..8c0a9327 --- /dev/null +++ b/3rdparty/django-openid-auth/django_openid_auth/store.py @@ -0,0 +1,131 @@ +# django-openid-auth - OpenID integration for django.contrib.auth +# +# Copyright (C) 2007 Simon Willison +# Copyright (C) 2008-2010 Canonical Ltd. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +import base64 +import time + +from openid.association import Association as OIDAssociation +from openid.store.interface import OpenIDStore +from openid.store.nonce import SKEW + +from django_openid_auth.models import Association, Nonce + + +class DjangoOpenIDStore(OpenIDStore): + def __init__(self): + self.max_nonce_age = 6 * 60 * 60 # Six hours + + def storeAssociation(self, server_url, association): + try: + assoc = Association.objects.get( + server_url=server_url, handle=association.handle) + except Association.DoesNotExist: + assoc = Association( + server_url=server_url, + handle=association.handle, + secret=base64.encodestring(association.secret), + issued=association.issued, + lifetime=association.lifetime, + assoc_type=association.assoc_type) + else: + assoc.secret = base64.encodestring(association.secret) + assoc.issued = association.issued + assoc.lifetime = association.lifetime + assoc.assoc_type = association.assoc_type + assoc.save() + + def getAssociation(self, server_url, handle=None): + assocs = [] + if handle is not None: + assocs = Association.objects.filter( + server_url=server_url, handle=handle) + else: + assocs = Association.objects.filter(server_url=server_url) + associations = [] + expired = [] + for assoc in assocs: + association = OIDAssociation( + assoc.handle, base64.decodestring(assoc.secret), assoc.issued, + assoc.lifetime, assoc.assoc_type + ) + if association.getExpiresIn() == 0: + expired.append(assoc) + else: + associations.append((association.issued, association)) + for assoc in expired: + assoc.delete() + if not associations: + return None + associations.sort() + return associations[-1][1] + + def removeAssociation(self, server_url, handle): + assocs = list(Association.objects.filter( + server_url=server_url, handle=handle)) + assocs_exist = len(assocs) > 0 + for assoc in assocs: + assoc.delete() + return assocs_exist + + def useNonce(self, server_url, timestamp, salt): + if abs(timestamp - time.time()) > SKEW: + return False + + try: + ononce = Nonce.objects.get( + server_url__exact=server_url, + timestamp__exact=timestamp, + salt__exact=salt) + except Nonce.DoesNotExist: + ononce = Nonce( + server_url=server_url, + timestamp=timestamp, + salt=salt) + ononce.save() + return True + + return False + + def cleanupNonces(self, _now=None): + if _now is None: + _now = int(time.time()) + expired = Nonce.objects.filter(timestamp__lt=_now - SKEW) + count = expired.count() + if count: + expired.delete() + return count + + def cleanupAssociations(self): + now = int(time.time()) + expired = Association.objects.extra( + where=['issued + lifetime < %d' % now]) + count = expired.count() + if count: + expired.delete() + return count diff --git a/3rdparty/django-openid-auth/django_openid_auth/teams.py b/3rdparty/django-openid-auth/django_openid_auth/teams.py new file mode 100644 index 00000000..fc9782a9 --- /dev/null +++ b/3rdparty/django-openid-auth/django_openid_auth/teams.py @@ -0,0 +1,411 @@ +# Launchpad OpenID Teams Extension support for python-openid +# +# Copyright (C) 2008-2010 Canonical Ltd. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +"""Team membership support for Launchpad. + +The primary form of communication between the RP and Launchpad is an +OpenID authentication request. Our solution is to piggyback a team +membership test onto this interaction. + +As part of an OpenID authentication request, the RP includes the +following fields: + + openid.ns.lp: + An OpenID 2.0 namespace URI for the extension. It is not strictly + required for 1.1 requests, but including it is good for forward + compatibility. + + It must be set to: http://ns.launchpad.net/2007/openid-teams + + openid.lp.query_membership: + A comma separated list of Launchpad team names that the RP is + interested in. + +As part of the positive assertion OpenID response, the following field +will be provided: + + openid.ns.lp: + (as above) + + openid.lp.is_member: + A comma separated list of teams that the user is actually a member + of. The list may be limited to those teams mentioned in the + request. + + This field must be included in the response signature in order to + be considered valid (as the response is bounced through the user's + web browser, an unsigned value could be modified). + +@since: 2.1.1 +""" + +from openid.message import registerNamespaceAlias, \ + NamespaceAliasRegistrationError +from openid.extension import Extension +from openid import oidutil + +try: + basestring #pylint:disable-msg=W0104 +except NameError: + # For Python 2.2 + basestring = (str, unicode) #pylint:disable-msg=W0622 + +__all__ = [ + 'TeamsRequest', + 'TeamsResponse', + 'ns_uri', + 'supportsTeams', + ] + +ns_uri = 'http://ns.launchpad.net/2007/openid-teams' + +try: + registerNamespaceAlias(ns_uri, 'lp') +except NamespaceAliasRegistrationError, e: + oidutil.log('registerNamespaceAlias(%r, %r) failed: %s' % (ns_uri, + 'lp', str(e),)) + +def supportsTeams(endpoint): + """Does the given endpoint advertise support for Launchpad Teams? + + @param endpoint: The endpoint object as returned by OpenID discovery + @type endpoint: openid.consumer.discover.OpenIDEndpoint + + @returns: Whether an lp type was advertised by the endpoint + @rtype: bool + """ + return endpoint.usesExtension(ns_uri) + +class TeamsNamespaceError(ValueError): + """The Launchpad teams namespace was not found and could not + be created using the expected name (there's another extension + using the name 'lp') + + This is not I{illegal}, for OpenID 2, although it probably + indicates a problem, since it's not expected that other extensions + will re-use the alias that is in use for OpenID 1. + + If this is an OpenID 1 request, then there is no recourse. This + should not happen unless some code has modified the namespaces for + the message that is being processed. + """ + +def getTeamsNS(message): + """Extract the Launchpad teams namespace URI from the given + OpenID message. + + @param message: The OpenID message from which to parse Launchpad + teams. This may be a request or response message. + @type message: C{L{openid.message.Message}} + + @returns: the lp namespace URI for the supplied message. The + message may be modified to define a Launchpad teams + namespace. + @rtype: C{str} + + @raise ValueError: when using OpenID 1 if the message defines + the 'lp' alias to be something other than a Launchpad + teams type. + """ + # See if there exists an alias for the Launchpad teams type. + alias = message.namespaces.getAlias(ns_uri) + if alias is None: + # There is no alias, so try to add one. (OpenID version 1) + try: + message.namespaces.addAlias(ns_uri, 'lp') + except KeyError, why: + # An alias for the string 'lp' already exists, but it's + # defined for something other than Launchpad teams + raise TeamsNamespaceError(why[0]) + + # we know that ns_uri defined, because it's defined in the + # else clause of the loop as well, so disable the warning + return ns_uri #pylint:disable-msg=W0631 + +class TeamsRequest(Extension): + """An object to hold the state of a Launchpad teams request. + + @ivar query_membership: A comma separated list of Launchpad team + names that the RP is interested in. + @type required: [str] + + @group Consumer: requestField, requestTeams, getExtensionArgs, addToOpenIDRequest + @group Server: fromOpenIDRequest, parseExtensionArgs + """ + + ns_alias = 'lp' + + def __init__(self, query_membership=None, lp_ns_uri=ns_uri): + """Initialize an empty Launchpad teams request""" + Extension.__init__(self) + self.query_membership = [] + self.ns_uri = lp_ns_uri + + if query_membership: + self.requestTeams(query_membership) + + # Assign getTeamsNS to a static method so that it can be + # overridden for testing. + _getTeamsNS = staticmethod(getTeamsNS) + + def fromOpenIDRequest(cls, request): + """Create a Launchpad teams request that contains the + fields that were requested in the OpenID request with the + given arguments + + @param request: The OpenID request + @type request: openid.server.CheckIDRequest + + @returns: The newly created Launchpad teams request + @rtype: C{L{TeamsRequest}} + """ + self = cls() + + # Since we're going to mess with namespace URI mapping, don't + # mutate the object that was passed in. + message = request.message.copy() + + self.ns_uri = self._getTeamsNS(message) + args = message.getArgs(self.ns_uri) + self.parseExtensionArgs(args) + + return self + + fromOpenIDRequest = classmethod(fromOpenIDRequest) + + def parseExtensionArgs(self, args, strict=False): + """Parse the unqualified Launchpad teams request + parameters and add them to this object. + + This method is essentially the inverse of + C{L{getExtensionArgs}}. This method restores the serialized + Launchpad teams request fields. + + If you are extracting arguments from a standard OpenID + checkid_* request, you probably want to use C{L{fromOpenIDRequest}}, + which will extract the lp namespace and arguments from the + OpenID request. This method is intended for cases where the + OpenID server needs more control over how the arguments are + parsed than that method provides. + + >>> args = message.getArgs(ns_uri) + >>> request.parseExtensionArgs(args) + + @param args: The unqualified Launchpad teams arguments + @type args: {str:str} + + @param strict: Whether requests with fields that are not + defined in the Launchpad teams specification should be + tolerated (and ignored) + @type strict: bool + + @returns: None; updates this object + """ + items = args.get('query_membership') + if items: + for team_name in items.split(','): + try: + self.requestTeam(team_name, strict) + except ValueError: + if strict: + raise + + def allRequestedTeams(self): + """A list of all of the Launchpad teams that were + requested. + + @rtype: [str] + """ + return self.query_membership + + def wereTeamsRequested(self): + """Have any Launchpad teams been requested? + + @rtype: bool + """ + return bool(self.allRequestedTeams()) + + def __contains__(self, team_name): + """Was this team in the request?""" + return team_name in self.query_membership + + def requestTeam(self, team_name, strict=False): + """Request the specified team from the OpenID user + + @param team_name: the unqualified Launchpad team name + @type team_name: str + + @param strict: whether to raise an exception when a team is + added to a request more than once + + @raise ValueError: when strict is set and the team was + requested more than once + """ + if strict: + if team_name in self.query_membership: + raise ValueError('That team has already been requested') + else: + if team_name in self.query_membership: + return + + self.query_membership.append(team_name) + + def requestTeams(self, query_membership, strict=False): + """Add the given list of teams to the request + + @param query_membership: The Launchpad teams request + @type query_membership: [str] + + @raise ValueError: when a team requested is not a string + or strict is set and a team was requested more than once + """ + if isinstance(query_membership, basestring): + raise TypeError('Teams should be passed as a list of ' + 'strings (not %r)' % (type(query_membership),)) + + for team_name in query_membership: + self.requestTeam(team_name, strict=strict) + + def getExtensionArgs(self): + """Get a dictionary of unqualified Launchpad teams + arguments representing this request. + + This method is essentially the inverse of + C{L{parseExtensionArgs}}. This method serializes the Launchpad + teams request fields. + + @rtype: {str:str} + """ + args = {} + + if self.query_membership: + args['query_membership'] = ','.join(self.query_membership) + + return args + +class TeamsResponse(Extension): + """Represents the data returned in a Launchpad teams response + inside of an OpenID C{id_res} response. This object will be + created by the OpenID server, added to the C{id_res} response + object, and then extracted from the C{id_res} message by the + Consumer. + + @ivar data: The Launchpad teams data, an array. + + @ivar ns_uri: The URI under which the Launchpad teams data was + stored in the response message. + + @group Server: extractResponse + @group Consumer: fromSuccessResponse + @group Read-only dictionary interface: keys, iterkeys, items, iteritems, + __iter__, get, __getitem__, keys, has_key + """ + + ns_alias = 'lp' + + def __init__(self, is_member=None, lp_ns_uri=ns_uri): + Extension.__init__(self) + if is_member is None: + self.is_member = [] + else: + self.is_member = is_member + + self.ns_uri = lp_ns_uri + + def addTeam(self, team_name): + if team_name not in self.is_member: + self.is_member.append(team_name) + + def extractResponse(cls, request, is_member_str): + """Take a C{L{TeamsRequest}} and a list of Launchpad + team values and create a C{L{TeamsResponse}} + object containing that data. + + @param request: The Launchpad teams request object + @type request: TeamsRequest + + @param is_member: The Launchpad teams data for this + response, as a list of strings. + @type is_member: {str:str} + + @returns: a Launchpad teams response object + @rtype: TeamsResponse + """ + self = cls() + self.ns_uri = request.ns_uri + self.is_member = is_member_str.split(',') + return self + + extractResponse = classmethod(extractResponse) + + # Assign getTeamsNS to a static method so that it can be + # overridden for testing + _getTeamsNS = staticmethod(getTeamsNS) + + def fromSuccessResponse(cls, success_response, signed_only=True): + """Create a C{L{TeamsResponse}} object from a successful OpenID + library response + (C{L{openid.consumer.consumer.SuccessResponse}}) response + message + + @param success_response: A SuccessResponse from consumer.complete() + @type success_response: C{L{openid.consumer.consumer.SuccessResponse}} + + @param signed_only: Whether to process only data that was + signed in the id_res message from the server. + @type signed_only: bool + + @rtype: TeamsResponse + @returns: A Launchpad teams response containing the data + that was supplied with the C{id_res} response. + """ + self = cls() + self.ns_uri = self._getTeamsNS(success_response.message) + if signed_only: + args = success_response.getSignedNS(self.ns_uri) + else: + args = success_response.message.getArgs(self.ns_uri) + + if "is_member" in args: + is_member_str = args["is_member"] + self.is_member = is_member_str.split(',') + #self.is_member = args["is_member"] + + return self + + fromSuccessResponse = classmethod(fromSuccessResponse) + + def getExtensionArgs(self): + """Get the fields to put in the Launchpad teams namespace + when adding them to an id_res message. + + @see: openid.extension + """ + ns_args = {'is_member': ','.join(self.is_member),} + return ns_args + diff --git a/3rdparty/django-openid-auth/django_openid_auth/templates/openid/failure.html b/3rdparty/django-openid-auth/django_openid_auth/templates/openid/failure.html new file mode 100644 index 00000000..37710518 --- /dev/null +++ b/3rdparty/django-openid-auth/django_openid_auth/templates/openid/failure.html @@ -0,0 +1,11 @@ + + + + OpenID failed + + +

OpenID failed

+

{{ message|escape }}

+ + diff --git a/3rdparty/django-openid-auth/django_openid_auth/templates/openid/login.html b/3rdparty/django-openid-auth/django_openid_auth/templates/openid/login.html new file mode 100644 index 00000000..1d682dff --- /dev/null +++ b/3rdparty/django-openid-auth/django_openid_auth/templates/openid/login.html @@ -0,0 +1,43 @@ +{% load i18n %} + + + +Sign in with your OpenID + + + +

Sign in with your OpenID

+{% if form.errors %} +

{% trans "Please correct errors below:" %}
+ {% if form.openid_identifier.errors %} + {{ form.openid_identifier.errors|join:", " }} + {% endif %} + {% if form.next.errors %} + {{ form.next.errors|join:", " }} + {% endif %} +

+{% endif %} +
+ {% csrf_token %} +
+ {% trans "Sign In Using Your OpenID" %} +
+
+ {{ form.openid_identifier }} +
+
+ +
+ {% if next %} + + {% endif %} +
+
+ + diff --git a/3rdparty/django-openid-auth/django_openid_auth/tests/__init__.py b/3rdparty/django-openid-auth/django_openid_auth/tests/__init__.py new file mode 100644 index 00000000..9ac966ca --- /dev/null +++ b/3rdparty/django-openid-auth/django_openid_auth/tests/__init__.py @@ -0,0 +1,37 @@ +# django-openid-auth - OpenID integration for django.contrib.auth +# +# Copyright (C) 2009-2010 Canonical Ltd. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +import unittest + + +def suite(): + suite = unittest.TestSuite() + for name in ['test_auth', 'test_store', 'test_views']: + mod = __import__('%s.%s' % (__name__, name), {}, {}, ['suite']) + suite.addTest(mod.suite()) + return suite diff --git a/3rdparty/django-openid-auth/django_openid_auth/tests/test_auth.py b/3rdparty/django-openid-auth/django_openid_auth/tests/test_auth.py new file mode 100644 index 00000000..00a1470d --- /dev/null +++ b/3rdparty/django-openid-auth/django_openid_auth/tests/test_auth.py @@ -0,0 +1,133 @@ +# django-openid-auth - OpenID integration for django.contrib.auth +# +# Copyright (C) 2010 Canonical Ltd. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +import unittest + +from django.test import TestCase + +from django_openid_auth.auth import OpenIDBackend +from openid.consumer.consumer import SuccessResponse +from openid.consumer.discover import OpenIDServiceEndpoint +from openid.message import Message, OPENID2_NS + + +SREG_NS = "http://openid.net/sreg/1.0" +AX_NS = "http://openid.net/srv/ax/1.0" + +class OpenIDBackendTests(TestCase): + + def setUp(self): + super(OpenIDBackendTests, self).setUp() + self.backend = OpenIDBackend() + + def test_extract_user_details_sreg(self): + endpoint = OpenIDServiceEndpoint() + message = Message(OPENID2_NS) + message.setArg(SREG_NS, "nickname", "someuser") + message.setArg(SREG_NS, "fullname", "Some User") + message.setArg(SREG_NS, "email", "foo@example.com") + response = SuccessResponse( + endpoint, message, signed_fields=message.toPostArgs().keys()) + + data = self.backend._extract_user_details(response) + self.assertEqual(data, {"nickname": "someuser", + "first_name": "Some", + "last_name": "User", + "email": "foo@example.com"}) + + def test_extract_user_details_ax(self): + endpoint = OpenIDServiceEndpoint() + message = Message(OPENID2_NS) + attributes = [ + ("nickname", "http://axschema.org/namePerson/friendly", "someuser"), + ("fullname", "http://axschema.org/namePerson", "Some User"), + ("email", "http://axschema.org/contact/email", "foo@example.com"), + ] + message.setArg(AX_NS, "mode", "fetch_response") + for (alias, uri, value) in attributes: + message.setArg(AX_NS, "type.%s" % alias, uri) + message.setArg(AX_NS, "value.%s" % alias, value) + response = SuccessResponse( + endpoint, message, signed_fields=message.toPostArgs().keys()) + + data = self.backend._extract_user_details(response) + self.assertEqual(data, {"nickname": "someuser", + "first_name": "Some", + "last_name": "User", + "email": "foo@example.com"}) + + def test_extract_user_details_ax_split_name(self): + endpoint = OpenIDServiceEndpoint() + message = Message(OPENID2_NS) + attributes = [ + ("nickname", "http://axschema.org/namePerson/friendly", "someuser"), + # Include this key too to show that the split data takes + # precedence. + ("fullname", "http://axschema.org/namePerson", "Bad Data"), + ("first", "http://axschema.org/namePerson/first", "Some"), + ("last", "http://axschema.org/namePerson/last", "User"), + ("email", "http://axschema.org/contact/email", "foo@example.com"), + ] + message.setArg(AX_NS, "mode", "fetch_response") + for (alias, uri, value) in attributes: + message.setArg(AX_NS, "type.%s" % alias, uri) + message.setArg(AX_NS, "value.%s" % alias, value) + response = SuccessResponse( + endpoint, message, signed_fields=message.toPostArgs().keys()) + + data = self.backend._extract_user_details(response) + self.assertEqual(data, {"nickname": "someuser", + "first_name": "Some", + "last_name": "User", + "email": "foo@example.com"}) + + def test_extract_user_details_ax_broken_myopenid(self): + endpoint = OpenIDServiceEndpoint() + message = Message(OPENID2_NS) + attributes = [ + ("nickname", "http://schema.openid.net/namePerson/friendly", + "someuser"), + ("fullname", "http://schema.openid.net/namePerson", "Some User"), + ("email", "http://schema.openid.net/contact/email", + "foo@example.com"), + ] + message.setArg(AX_NS, "mode", "fetch_response") + for (alias, uri, value) in attributes: + message.setArg(AX_NS, "type.%s" % alias, uri) + message.setArg(AX_NS, "value.%s" % alias, value) + response = SuccessResponse( + endpoint, message, signed_fields=message.toPostArgs().keys()) + + data = self.backend._extract_user_details(response) + self.assertEqual(data, {"nickname": "someuser", + "first_name": "Some", + "last_name": "User", + "email": "foo@example.com"}) + +def suite(): + return unittest.TestLoader().loadTestsFromName(__name__) diff --git a/3rdparty/django-openid-auth/django_openid_auth/tests/test_store.py b/3rdparty/django-openid-auth/django_openid_auth/tests/test_store.py new file mode 100644 index 00000000..60625028 --- /dev/null +++ b/3rdparty/django-openid-auth/django_openid_auth/tests/test_store.py @@ -0,0 +1,193 @@ +# django-openid-auth - OpenID integration for django.contrib.auth +# +# Copyright (C) 2009-2010 Canonical Ltd. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +import time +import unittest + +from django.test import TestCase +from openid.association import Association as OIDAssociation +from openid.store.nonce import SKEW + +from django_openid_auth.models import Association, Nonce +from django_openid_auth.store import DjangoOpenIDStore + + +class OpenIDStoreTests(TestCase): + + def setUp(self): + super(OpenIDStoreTests, self).setUp() + self.store = DjangoOpenIDStore() + + def test_storeAssociation(self): + assoc = OIDAssociation('handle', 'secret', 42, 600, 'HMAC-SHA1') + self.store.storeAssociation('server-url', assoc) + + dbassoc = Association.objects.get( + server_url='server-url', handle='handle') + self.assertEquals(dbassoc.server_url, 'server-url') + self.assertEquals(dbassoc.handle, 'handle') + self.assertEquals(dbassoc.secret, 'secret'.encode('base-64')) + self.assertEquals(dbassoc.issued, 42) + self.assertEquals(dbassoc.lifetime, 600) + self.assertEquals(dbassoc.assoc_type, 'HMAC-SHA1') + + def test_storeAssociation_update_existing(self): + assoc = OIDAssociation('handle', 'secret', 42, 600, 'HMAC-SHA1') + self.store.storeAssociation('server-url', assoc) + + # Now update the association with new information. + assoc = OIDAssociation('handle', 'secret2', 420, 900, 'HMAC-SHA256') + self.store.storeAssociation('server-url', assoc) + dbassoc = Association.objects.get( + server_url='server-url', handle='handle') + self.assertEqual(dbassoc.secret, 'secret2'.encode('base-64')) + self.assertEqual(dbassoc.issued, 420) + self.assertEqual(dbassoc.lifetime, 900) + self.assertEqual(dbassoc.assoc_type, 'HMAC-SHA256') + + def test_getAssociation(self): + timestamp = int(time.time()) + self.store.storeAssociation( + 'server-url', OIDAssociation('handle', 'secret', timestamp, 600, + 'HMAC-SHA1')) + assoc = self.store.getAssociation('server-url', 'handle') + self.assertTrue(isinstance(assoc, OIDAssociation)) + + self.assertEquals(assoc.handle, 'handle') + self.assertEquals(assoc.secret, 'secret') + self.assertEquals(assoc.issued, timestamp) + self.assertEquals(assoc.lifetime, 600) + self.assertEquals(assoc.assoc_type, 'HMAC-SHA1') + + def test_getAssociation_unknown(self): + assoc = self.store.getAssociation('server-url', 'unknown') + self.assertEquals(assoc, None) + + def test_getAssociation_expired(self): + lifetime = 600 + timestamp = int(time.time()) - 2 * lifetime + self.store.storeAssociation( + 'server-url', OIDAssociation('handle', 'secret', timestamp, + lifetime, 'HMAC-SHA1')) + + # The association is not returned, and is removed from the database. + assoc = self.store.getAssociation('server-url', 'handle') + self.assertEquals(assoc, None) + self.assertRaises(Association.DoesNotExist, Association.objects.get, + server_url='server-url', handle='handle') + + def test_getAssociation_no_handle(self): + timestamp = int(time.time()) + + self.store.storeAssociation( + 'server-url', OIDAssociation('handle1', 'secret', timestamp + 1, + 600, 'HMAC-SHA1')) + self.store.storeAssociation( + 'server-url', OIDAssociation('handle2', 'secret', timestamp, + 600, 'HMAC-SHA1')) + + # The newest handle is returned. + assoc = self.store.getAssociation('server-url', None) + self.assertNotEquals(assoc, None) + self.assertEquals(assoc.handle, 'handle1') + self.assertEquals(assoc.issued, timestamp + 1) + + def test_removeAssociation(self): + timestamp = int(time.time()) + self.store.storeAssociation( + 'server-url', OIDAssociation('handle', 'secret', timestamp, 600, + 'HMAC-SHA1')) + self.assertEquals( + self.store.removeAssociation('server-url', 'handle'), True) + self.assertEquals( + self.store.getAssociation('server-url', 'handle'), None) + + def test_removeAssociation_unknown(self): + self.assertEquals( + self.store.removeAssociation('server-url', 'unknown'), False) + + def test_useNonce(self): + timestamp = time.time() + # The nonce can only be used once. + self.assertEqual( + self.store.useNonce('server-url', timestamp, 'salt'), True) + self.assertEqual( + self.store.useNonce('server-url', timestamp, 'salt'), False) + self.assertEqual( + self.store.useNonce('server-url', timestamp, 'salt'), False) + + def test_useNonce_expired(self): + timestamp = time.time() - 2 * SKEW + self.assertEqual( + self.store.useNonce('server-url', timestamp, 'salt'), False) + + def test_useNonce_future(self): + timestamp = time.time() + 2 * SKEW + self.assertEqual( + self.store.useNonce('server-url', timestamp, 'salt'), False) + + def test_cleanupNonces(self): + timestamp = time.time() + self.assertEqual( + self.store.useNonce('server1', timestamp, 'salt1'), True) + self.assertEqual( + self.store.useNonce('server2', timestamp, 'salt2'), True) + self.assertEqual( + self.store.useNonce('server3', timestamp, 'salt3'), True) + self.assertEqual(Nonce.objects.count(), 3) + + self.assertEqual( + self.store.cleanupNonces(_now=timestamp + 2 * SKEW), 3) + self.assertEqual(Nonce.objects.count(), 0) + + # The nonces have now been cleared: + self.assertEqual( + self.store.useNonce('server1', timestamp, 'salt1'), True) + self.assertEqual( + self.store.cleanupNonces(_now=timestamp + 2 * SKEW), 1) + self.assertEqual( + self.store.cleanupNonces(_now=timestamp + 2 * SKEW), 0) + + def test_cleanupAssociations(self): + timestamp = int(time.time()) - 100 + self.store.storeAssociation( + 'server-url', OIDAssociation('handle1', 'secret', timestamp, + 50, 'HMAC-SHA1')) + self.store.storeAssociation( + 'server-url', OIDAssociation('handle2', 'secret', timestamp, + 200, 'HMAC-SHA1')) + + self.assertEquals(self.store.cleanupAssociations(), 1) + + # The second (non-expired) association is left behind. + self.assertNotEqual(self.store.getAssociation('server-url', 'handle2'), + None) + + +def suite(): + return unittest.TestLoader().loadTestsFromName(__name__) diff --git a/3rdparty/django-openid-auth/django_openid_auth/tests/test_views.py b/3rdparty/django-openid-auth/django_openid_auth/tests/test_views.py new file mode 100644 index 00000000..119da9c7 --- /dev/null +++ b/3rdparty/django-openid-auth/django_openid_auth/tests/test_views.py @@ -0,0 +1,549 @@ +# django-openid-auth - OpenID integration for django.contrib.auth +# +# Copyright (C) 2009-2010 Canonical Ltd. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +import cgi +import re +import time +import unittest + +from django.conf import settings +from django.contrib.auth.models import User, Group +from django.test import TestCase +from openid.extensions import ax, sreg +from openid.fetchers import ( + HTTPFetcher, HTTPFetchingError, HTTPResponse, setDefaultFetcher) +from openid.oidutil import importElementTree +from openid.server.server import BROWSER_REQUEST_MODES, ENCODE_URL, Server +from openid.store.memstore import MemoryStore + +from django_openid_auth import teams +from django_openid_auth.models import UserOpenID +from django_openid_auth.views import sanitise_redirect_url + + +ET = importElementTree() + + +class StubOpenIDProvider(HTTPFetcher): + + def __init__(self, base_url): + self.store = MemoryStore() + self.identity_url = base_url + 'identity' + self.localid_url = base_url + 'localid' + self.endpoint_url = base_url + 'endpoint' + self.server = Server(self.store, self.endpoint_url) + self.last_request = None + self.type_uris = ['http://specs.openid.net/auth/2.0/signon'] + + def fetch(self, url, body=None, headers=None): + if url == self.identity_url: + # Serve an XRDS document directly, pointing at our endpoint. + type_uris = ['%s' % uri for uri in self.type_uris] + return HTTPResponse( + url, 200, {'content-type': 'application/xrds+xml'}, """\ + + + + + %s + %s + %s + + + +""" % ('\n'.join(type_uris), self.endpoint_url, self.localid_url)) + elif url.startswith(self.endpoint_url): + # Gather query parameters + query = {} + if '?' in url: + query.update(cgi.parse_qsl(url.split('?', 1)[1])) + if body is not None: + query.update(cgi.parse_qsl(body)) + self.last_request = self.server.decodeRequest(query) + + # The browser based requests should not be handled through + # the fetcher interface. + assert self.last_request.mode not in BROWSER_REQUEST_MODES + + response = self.server.handleRequest(self.last_request) + webresponse = self.server.encodeResponse(response) + return HTTPResponse(url, webresponse.code, webresponse.headers, + webresponse.body) + else: + raise HTTPFetchingError('unknown URL %s' % url) + + def parseFormPost(self, content): + """Parse an HTML form post to create an OpenID request.""" + # Hack to make the javascript XML compliant ... + content = content.replace('i < elements.length', + 'i < elements.length') + tree = ET.XML(content) + form = tree.find('.//form') + assert form is not None, 'No form in document' + assert form.get('action') == self.endpoint_url, ( + 'Form posts to %s instead of %s' % (form.get('action'), + self.endpoint_url)) + query = {} + for input in form.findall('input'): + if input.get('type') != 'hidden': + continue + query[input.get('name').encode('UTF-8')] = \ + input.get('value').encode('UTF-8') + self.last_request = self.server.decodeRequest(query) + return self.last_request + + +class RelyingPartyTests(TestCase): + urls = 'django_openid_auth.tests.urls' + + def setUp(self): + super(RelyingPartyTests, self).setUp() + self.provider = StubOpenIDProvider('http://example.com/') + setDefaultFetcher(self.provider, wrap_exceptions=False) + + self.old_login_redirect_url = getattr(settings, 'LOGIN_REDIRECT_URL', '/accounts/profile/') + self.old_create_users = getattr(settings, 'OPENID_CREATE_USERS', False) + self.old_update_details = getattr(settings, 'OPENID_UPDATE_DETAILS_FROM_SREG', False) + self.old_sso_server_url = getattr(settings, 'OPENID_SSO_SERVER_URL', None) + self.old_teams_map = getattr(settings, 'OPENID_LAUNCHPAD_TEAMS_MAPPING', {}) + self.old_use_as_admin_login = getattr(settings, 'OPENID_USE_AS_ADMIN_LOGIN', False) + + settings.OPENID_CREATE_USERS = False + settings.OPENID_UPDATE_DETAILS_FROM_SREG = False + settings.OPENID_SSO_SERVER_URL = None + settings.OPENID_LAUNCHPAD_TEAMS_MAPPING = {} + settings.OPENID_USE_AS_ADMIN_LOGIN = False + + def tearDown(self): + settings.LOGIN_REDIRECT_URL = self.old_login_redirect_url + settings.OPENID_CREATE_USERS = self.old_create_users + settings.OPENID_UPDATE_DETAILS_FROM_SREG = self.old_update_details + settings.OPENID_SSO_SERVER_URL = self.old_sso_server_url + settings.OPENID_LAUNCHPAD_TEAMS_MAPPING = self.old_teams_map + settings.OPENID_USE_AS_ADMIN_LOGIN = self.old_use_as_admin_login + + setDefaultFetcher(None) + super(RelyingPartyTests, self).tearDown() + + def complete(self, openid_response): + """Complete an OpenID authentication request.""" + # The server can generate either a redirect or a form post + # here. For simplicity, force generation of a redirect. + openid_response.whichEncoding = lambda: ENCODE_URL + webresponse = self.provider.server.encodeResponse(openid_response) + self.assertEquals(webresponse.code, 302) + redirect_to = webresponse.headers['location'] + self.assertTrue(redirect_to.startswith( + 'http://testserver/openid/complete/')) + return self.client.get('/openid/complete/', + dict(cgi.parse_qsl(redirect_to.split('?', 1)[1]))) + + def test_login(self): + user = User.objects.create_user('someuser', 'someone@example.com') + useropenid = UserOpenID( + user=user, + claimed_id='http://example.com/identity', + display_id='http://example.com/identity') + useropenid.save() + + # The login form is displayed: + response = self.client.get('/openid/login/') + self.assertTemplateUsed(response, 'openid/login.html') + + # Posting in an identity URL begins the authentication request: + response = self.client.post('/openid/login/', + {'openid_identifier': 'http://example.com/identity', + 'next': '/getuser/'}) + self.assertContains(response, 'OpenID transaction in progress') + + openid_request = self.provider.parseFormPost(response.content) + self.assertEquals(openid_request.mode, 'checkid_setup') + self.assertTrue(openid_request.return_to.startswith( + 'http://testserver/openid/complete/')) + + # Complete the request. The user is redirected to the next URL. + openid_response = openid_request.answer(True) + response = self.complete(openid_response) + self.assertRedirects(response, 'http://testserver/getuser/') + + # And they are now logged in: + response = self.client.get('/getuser/') + self.assertEquals(response.content, 'someuser') + + def test_login_no_next(self): + """Logins with no next parameter redirect to LOGIN_REDIRECT_URL.""" + user = User.objects.create_user('someuser', 'someone@example.com') + useropenid = UserOpenID( + user=user, + claimed_id='http://example.com/identity', + display_id='http://example.com/identity') + useropenid.save() + + settings.LOGIN_REDIRECT_URL = '/getuser/' + response = self.client.post('/openid/login/', + {'openid_identifier': 'http://example.com/identity'}) + self.assertContains(response, 'OpenID transaction in progress') + + openid_request = self.provider.parseFormPost(response.content) + self.assertEquals(openid_request.mode, 'checkid_setup') + self.assertTrue(openid_request.return_to.startswith( + 'http://testserver/openid/complete/')) + + # Complete the request. The user is redirected to the next URL. + openid_response = openid_request.answer(True) + response = self.complete(openid_response) + self.assertRedirects( + response, 'http://testserver' + settings.LOGIN_REDIRECT_URL) + + def test_login_sso(self): + settings.OPENID_SSO_SERVER_URL = 'http://example.com/identity' + user = User.objects.create_user('someuser', 'someone@example.com') + useropenid = UserOpenID( + user=user, + claimed_id='http://example.com/identity', + display_id='http://example.com/identity') + useropenid.save() + + # Requesting the login form immediately begins an + # authentication request. + response = self.client.get('/openid/login/', {'next': '/getuser/'}) + self.assertEquals(response.status_code, 200) + self.assertContains(response, 'OpenID transaction in progress') + + openid_request = self.provider.parseFormPost(response.content) + self.assertEquals(openid_request.mode, 'checkid_setup') + self.assertTrue(openid_request.return_to.startswith( + 'http://testserver/openid/complete/')) + + # Complete the request. The user is redirected to the next URL. + openid_response = openid_request.answer(True) + response = self.complete(openid_response) + self.assertRedirects(response, 'http://testserver/getuser/') + + # And they are now logged in: + response = self.client.get('/getuser/') + self.assertEquals(response.content, 'someuser') + + def test_login_create_users(self): + settings.OPENID_CREATE_USERS = True + # Create a user with the same name as we'll pass back via sreg. + User.objects.create_user('someuser', 'someone@example.com') + + # Posting in an identity URL begins the authentication request: + response = self.client.post('/openid/login/', + {'openid_identifier': 'http://example.com/identity', + 'next': '/getuser/'}) + self.assertContains(response, 'OpenID transaction in progress') + + # Complete the request, passing back some simple registration + # data. The user is redirected to the next URL. + openid_request = self.provider.parseFormPost(response.content) + sreg_request = sreg.SRegRequest.fromOpenIDRequest(openid_request) + openid_response = openid_request.answer(True) + sreg_response = sreg.SRegResponse.extractResponse( + sreg_request, {'nickname': 'someuser', 'fullname': 'Some User', + 'email': 'foo@example.com'}) + openid_response.addExtension(sreg_response) + response = self.complete(openid_response) + self.assertRedirects(response, 'http://testserver/getuser/') + + # And they are now logged in as a new user (they haven't taken + # over the existing "someuser" user). + response = self.client.get('/getuser/') + self.assertEquals(response.content, 'someuser2') + + # Check the details of the new user. + user = User.objects.get(username='someuser2') + self.assertEquals(user.first_name, 'Some') + self.assertEquals(user.last_name, 'User') + self.assertEquals(user.email, 'foo@example.com') + + def test_login_update_details(self): + settings.OPENID_UPDATE_DETAILS_FROM_SREG = True + user = User.objects.create_user('testuser', 'someone@example.com') + useropenid = UserOpenID( + user=user, + claimed_id='http://example.com/identity', + display_id='http://example.com/identity') + useropenid.save() + + # Posting in an identity URL begins the authentication request: + response = self.client.post('/openid/login/', + {'openid_identifier': 'http://example.com/identity', + 'next': '/getuser/'}) + self.assertContains(response, 'OpenID transaction in progress') + + # Complete the request, passing back some simple registration + # data. The user is redirected to the next URL. + openid_request = self.provider.parseFormPost(response.content) + sreg_request = sreg.SRegRequest.fromOpenIDRequest(openid_request) + openid_response = openid_request.answer(True) + sreg_response = sreg.SRegResponse.extractResponse( + sreg_request, {'nickname': 'someuser', 'fullname': 'Some User', + 'email': 'foo@example.com'}) + openid_response.addExtension(sreg_response) + response = self.complete(openid_response) + self.assertRedirects(response, 'http://testserver/getuser/') + + # And they are now logged in as testuser (the passed in + # nickname has not caused the username to change). + response = self.client.get('/getuser/') + self.assertEquals(response.content, 'testuser') + + # The user's full name and email have been updated. + user = User.objects.get(username='testuser') + self.assertEquals(user.first_name, 'Some') + self.assertEquals(user.last_name, 'User') + self.assertEquals(user.email, 'foo@example.com') + + def test_login_attribute_exchange(self): + settings.OPENID_UPDATE_DETAILS_FROM_SREG = True + user = User.objects.create_user('testuser', 'someone@example.com') + useropenid = UserOpenID( + user=user, + claimed_id='http://example.com/identity', + display_id='http://example.com/identity') + useropenid.save() + + # Configure the provider to advertise attribute exchange + # protocol and start the authentication process: + self.provider.type_uris.append('http://openid.net/srv/ax/1.0') + response = self.client.post('/openid/login/', + {'openid_identifier': 'http://example.com/identity', + 'next': '/getuser/'}) + self.assertContains(response, 'OpenID transaction in progress') + + # The resulting OpenID request uses the Attribute Exchange + # extension rather than the Simple Registration extension. + openid_request = self.provider.parseFormPost(response.content) + sreg_request = sreg.SRegRequest.fromOpenIDRequest(openid_request) + self.assertEqual(sreg_request.required, []) + self.assertEqual(sreg_request.optional, []) + + fetch_request = ax.FetchRequest.fromOpenIDRequest(openid_request) + self.assertTrue(fetch_request.has_key( + 'http://axschema.org/contact/email')) + self.assertTrue(fetch_request.has_key( + 'http://axschema.org/namePerson')) + self.assertTrue(fetch_request.has_key( + 'http://axschema.org/namePerson/first')) + self.assertTrue(fetch_request.has_key( + 'http://axschema.org/namePerson/last')) + self.assertTrue(fetch_request.has_key( + 'http://axschema.org/namePerson/friendly')) + # myOpenID compatibilty attributes: + self.assertTrue(fetch_request.has_key( + 'http://schema.openid.net/contact/email')) + self.assertTrue(fetch_request.has_key( + 'http://schema.openid.net/namePerson')) + self.assertTrue(fetch_request.has_key( + 'http://schema.openid.net/namePerson/friendly')) + + # Build up a response including AX data. + openid_response = openid_request.answer(True) + fetch_response = ax.FetchResponse(fetch_request) + fetch_response.addValue( + 'http://axschema.org/contact/email', 'foo@example.com') + fetch_response.addValue( + 'http://axschema.org/namePerson/first', 'Firstname') + fetch_response.addValue( + 'http://axschema.org/namePerson/last', 'Lastname') + fetch_response.addValue( + 'http://axschema.org/namePerson/friendly', 'someuser') + openid_response.addExtension(fetch_response) + response = self.complete(openid_response) + self.assertRedirects(response, 'http://testserver/getuser/') + + # And they are now logged in as testuser (the passed in + # nickname has not caused the username to change). + response = self.client.get('/getuser/') + self.assertEquals(response.content, 'testuser') + + # The user's full name and email have been updated. + user = User.objects.get(username='testuser') + self.assertEquals(user.first_name, 'Firstname') + self.assertEquals(user.last_name, 'Lastname') + self.assertEquals(user.email, 'foo@example.com') + + def test_login_teams(self): + settings.OPENID_LAUNCHPAD_TEAMS_MAPPING = {'teamname': 'groupname', + 'otherteam': 'othergroup'} + user = User.objects.create_user('testuser', 'someone@example.com') + group = Group(name='groupname') + group.save() + ogroup = Group(name='othergroup') + ogroup.save() + user.groups.add(ogroup) + user.save() + useropenid = UserOpenID( + user=user, + claimed_id='http://example.com/identity', + display_id='http://example.com/identity') + useropenid.save() + + # Posting in an identity URL begins the authentication request: + response = self.client.post('/openid/login/', + {'openid_identifier': 'http://example.com/identity', + 'next': '/getuser/'}) + self.assertContains(response, 'OpenID transaction in progress') + + # Complete the request + openid_request = self.provider.parseFormPost(response.content) + openid_response = openid_request.answer(True) + teams_request = teams.TeamsRequest.fromOpenIDRequest(openid_request) + teams_response = teams.TeamsResponse.extractResponse( + teams_request, 'teamname,some-other-team') + openid_response.addExtension(teams_response) + response = self.complete(openid_response) + self.assertRedirects(response, 'http://testserver/getuser/') + + # And they are now logged in as testuser + response = self.client.get('/getuser/') + self.assertEquals(response.content, 'testuser') + + # The user's groups have been updated. + user = User.objects.get(username='testuser') + self.assertTrue(group in user.groups.all()) + self.assertTrue(ogroup not in user.groups.all()) + + def test_login_teams_automapping(self): + settings.OPENID_LAUNCHPAD_TEAMS_MAPPING = {'teamname': 'groupname', + 'otherteam': 'othergroup'} + settings.OPENID_LAUNCHPAD_TEAMS_MAPPING_AUTO = True + settings.OPENID_LAUNCHPAD_TEAMS_MAPPING_AUTO_BLACKLIST = ['django-group1', 'django-group2'] + user = User.objects.create_user('testuser', 'someone@example.com') + group1 = Group(name='django-group1') + group1.save() + group2 = Group(name='django-group2') + group2.save() + group3 = Group(name='django-group3') + group3.save() + user.save() + useropenid = UserOpenID( + user=user, + claimed_id='http://example.com/identity', + display_id='http://example.com/identity') + useropenid.save() + + # Posting in an identity URL begins the authentication request: + response = self.client.post('/openid/login/', + {'openid_identifier': 'http://example.com/identity', + 'next': '/getuser/'}) + self.assertContains(response, 'OpenID transaction in progress') + + # Complete the request + openid_request = self.provider.parseFormPost(response.content) + openid_response = openid_request.answer(True) + teams_request = teams.TeamsRequest.fromOpenIDRequest(openid_request) + + self.assertEqual(group1 in user.groups.all(), False) + self.assertEqual(group2 in user.groups.all(), False) + self.assertTrue(group3 not in user.groups.all()) + + def test_login_teams_staff_not_defined(self): + delattr(settings, 'OPENID_LAUNCHPAD_STAFF_TEAMS') + user = User.objects.create_user('testuser', 'someone@example.com') + user.is_staff = True + user.save() + self.assertTrue(user.is_staff) + + user = self.get_openid_authed_user_with_teams(user, 'teamname,some-other-team') + self.assertTrue(user.is_staff) + + def test_login_teams_staff_assignment(self): + settings.OPENID_LAUNCHPAD_STAFF_TEAMS = ('teamname',) + user = User.objects.create_user('testuser', 'someone@example.com') + user.is_staff = False + user.save() + self.assertFalse(user.is_staff) + + user = self.get_openid_authed_user_with_teams(user, 'teamname,some-other-team') + self.assertTrue(user.is_staff) + + def test_login_teams_staff_unassignment(self): + settings.OPENID_LAUNCHPAD_STAFF_TEAMS = ('different-teamname',) + user = User.objects.create_user('testuser', 'someone@example.com') + user.is_staff = True + user.save() + self.assertTrue(user.is_staff) + + user = self.get_openid_authed_user_with_teams(user, 'teamname,some-other-team') + self.assertFalse(user.is_staff) + + def get_openid_authed_user_with_teams(self, user, teams_str): + useropenid = UserOpenID( + user=user, + claimed_id='http://example.com/identity', + display_id='http://example.com/identity') + useropenid.save() + + # Posting in an identity URL begins the authentication request: + response = self.client.post('/openid/login/', + {'openid_identifier': 'http://example.com/identity'}) + + # Complete the request + openid_request = self.provider.parseFormPost(response.content) + openid_response = openid_request.answer(True) + teams_request = teams.TeamsRequest.fromOpenIDRequest(openid_request) + teams_response = teams.TeamsResponse.extractResponse( + teams_request, teams_str) + openid_response.addExtension(teams_response) + response = self.complete(openid_response) + return User.objects.get(username=user.username) + + +class HelperFunctionsTest(TestCase): + def test_sanitise_redirect_url(self): + settings.ALLOWED_EXTERNAL_OPENID_REDIRECT_DOMAINS = [ + "example.com", "example.org"] + # list of URLs and whether they should be passed or not + urls = [ + ("http://example.com", True), + ("http://example.org/", True), + ("http://example.org/foo/bar", True), + ("http://example.org/foo/bar?baz=quux", True), + ("http://example.org:9999/foo/bar?baz=quux", True), + ("http://www.example.org/", False), + ("http://example.net/foo/bar?baz=quux", False), + ("/somewhere/local", True), + ("/somewhere/local?url=http://fail.com/bar", True), + # An empty path, as seen when no "next" parameter is passed. + ("", False), + ("/path with spaces", False), + ] + for url, returns_self in urls: + sanitised = sanitise_redirect_url(url) + if returns_self: + self.assertEqual(url, sanitised) + else: + self.assertEqual(settings.LOGIN_REDIRECT_URL, sanitised) + +def suite(): + return unittest.TestLoader().loadTestsFromName(__name__) diff --git a/3rdparty/django-openid-auth/django_openid_auth/tests/urls.py b/3rdparty/django-openid-auth/django_openid_auth/tests/urls.py new file mode 100644 index 00000000..9403be54 --- /dev/null +++ b/3rdparty/django-openid-auth/django_openid_auth/tests/urls.py @@ -0,0 +1,39 @@ +# django-openid-auth - OpenID integration for django.contrib.auth +# +# Copyright (C) 2009-2010 Canonical Ltd. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +from django.http import HttpResponse +from django.conf.urls.defaults import * + + +def get_user(request): + return HttpResponse(request.user.username) + +urlpatterns = patterns('', + (r'^getuser/$', get_user), + (r'^openid/', include('django_openid_auth.urls')), +) diff --git a/3rdparty/django-openid-auth/django_openid_auth/urls.py b/3rdparty/django-openid-auth/django_openid_auth/urls.py new file mode 100644 index 00000000..a792cebf --- /dev/null +++ b/3rdparty/django-openid-auth/django_openid_auth/urls.py @@ -0,0 +1,36 @@ +# django-openid-auth - OpenID integration for django.contrib.auth +# +# Copyright (C) 2007 Simon Willison +# Copyright (C) 2008-2010 Canonical Ltd. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +from django.conf.urls.defaults import * + +urlpatterns = patterns('django_openid_auth.views', + url(r'^login/$', 'login_begin', name='openid-login'), + url(r'^complete/$', 'login_complete', name='openid-complete'), + url(r'^logo.gif$', 'logo', name='openid-logo'), +) diff --git a/3rdparty/django-openid-auth/django_openid_auth/views.py b/3rdparty/django-openid-auth/django_openid_auth/views.py new file mode 100644 index 00000000..950ca9e5 --- /dev/null +++ b/3rdparty/django-openid-auth/django_openid_auth/views.py @@ -0,0 +1,268 @@ +# django-openid-auth - OpenID integration for django.contrib.auth +# +# Copyright (C) 2007 Simon Willison +# Copyright (C) 2008-2010 Canonical Ltd. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +import re +import urllib +from urlparse import urlsplit + +from django.conf import settings +from django.contrib.auth import ( + REDIRECT_FIELD_NAME, authenticate, login as auth_login) +from django.contrib.auth.models import Group +from django.core.urlresolvers import reverse +from django.http import HttpResponse, HttpResponseRedirect +from django.shortcuts import render_to_response +from django.template import RequestContext +from django.template.loader import render_to_string +from django.views.decorators.csrf import csrf_exempt + +from openid.consumer.consumer import ( + Consumer, SUCCESS, CANCEL, FAILURE) +from openid.consumer.discover import DiscoveryFailure +from openid.extensions import sreg, ax + +from django_openid_auth import teams +from django_openid_auth.forms import OpenIDLoginForm +from django_openid_auth.store import DjangoOpenIDStore + + +next_url_re = re.compile('^/[-\w/]+$') + +def is_valid_next_url(next): + # When we allow this: + # /openid/?next=/welcome/ + # For security reasons we want to restrict the next= bit to being a local + # path, not a complete URL. + return bool(next_url_re.match(next)) + + +def sanitise_redirect_url(redirect_to): + """Sanitise the redirection URL.""" + # Light security check -- make sure redirect_to isn't garbage. + is_valid = True + if not redirect_to or ' ' in redirect_to: + is_valid = False + elif '//' in redirect_to: + # Allow the redirect URL to be external if it's a permitted domain + allowed_domains = getattr(settings, + "ALLOWED_EXTERNAL_OPENID_REDIRECT_DOMAINS", []) + s, netloc, p, q, f = urlsplit(redirect_to) + # allow it if netloc is blank or if the domain is allowed + if netloc: + # a domain was specified. Is it an allowed domain? + if netloc.find(":") != -1: + netloc, _ = netloc.split(":", 1) + if netloc not in allowed_domains: + is_valid = False + + # If the return_to URL is not valid, use the default. + if not is_valid: + redirect_to = settings.LOGIN_REDIRECT_URL + + return redirect_to + + +def make_consumer(request): + """Create an OpenID Consumer object for the given Django request.""" + # Give the OpenID library its own space in the session object. + session = request.session.setdefault('OPENID', {}) + store = DjangoOpenIDStore() + return Consumer(session, store) + + +def render_openid_request(request, openid_request, return_to, trust_root=None): + """Render an OpenID authentication request.""" + if trust_root is None: + trust_root = getattr(settings, 'OPENID_TRUST_ROOT', + request.build_absolute_uri('/')) + + if openid_request.shouldSendRedirect(): + redirect_url = openid_request.redirectURL( + trust_root, return_to) + return HttpResponseRedirect(redirect_url) + else: + form_html = openid_request.htmlMarkup( + trust_root, return_to, form_tag_attrs={'id': 'openid_message'}) + return HttpResponse(form_html, content_type='text/html;charset=UTF-8') + + +def default_render_failure(request, message, status=403, + template_name='openid/failure.html'): + """Render an error page to the user.""" + data = render_to_string( + template_name, dict(message=message), + context_instance=RequestContext(request)) + return HttpResponse(data, status=status) + + +def parse_openid_response(request): + """Parse an OpenID response from a Django request.""" + # Short cut if there is no request parameters. + #if len(request.REQUEST) == 0: + # return None + + current_url = request.build_absolute_uri() + + consumer = make_consumer(request) + return consumer.complete(dict(request.REQUEST.items()), current_url) + + +def login_begin(request, template_name='openid/login.html', + login_complete_view='openid-complete', + form_class=OpenIDLoginForm, + render_failure=default_render_failure, + redirect_field_name=REDIRECT_FIELD_NAME): + """Begin an OpenID login request, possibly asking for an identity URL.""" + redirect_to = request.REQUEST.get(redirect_field_name, '') + + # Get the OpenID URL to try. First see if we've been configured + # to use a fixed server URL. + openid_url = getattr(settings, 'OPENID_SSO_SERVER_URL', None) + + if openid_url is None: + if request.POST: + login_form = form_class(data=request.POST) + if login_form.is_valid(): + openid_url = login_form.cleaned_data['openid_identifier'] + else: + login_form = form_class() + + # Invalid or no form data: + if openid_url is None: + return render_to_response(template_name, { + 'form': login_form, + redirect_field_name: redirect_to + }, context_instance=RequestContext(request)) + + error = None + consumer = make_consumer(request) + try: + openid_request = consumer.begin(openid_url) + except DiscoveryFailure, exc: + return render_failure( + request, "OpenID discovery error: %s" % (str(exc),), status=500) + + # Request some user details. If the provider advertises support + # for attribute exchange, use that. + if openid_request.endpoint.supportsType(ax.AXMessage.ns_uri): + fetch_request = ax.FetchRequest() + # We mark all the attributes as required, since Google ignores + # optional attributes. We request both the full name and + # first/last components since some providers offer one but not + # the other. + for (attr, alias) in [ + ('http://axschema.org/contact/email', 'email'), + ('http://axschema.org/namePerson', 'fullname'), + ('http://axschema.org/namePerson/first', 'firstname'), + ('http://axschema.org/namePerson/last', 'lastname'), + ('http://axschema.org/namePerson/friendly', 'nickname'), + # The myOpenID provider advertises AX support, but uses + # attribute names from an obsolete draft of the + # specification. We request them for compatibility. + ('http://schema.openid.net/contact/email', 'old_email'), + ('http://schema.openid.net/namePerson', 'old_fullname'), + ('http://schema.openid.net/namePerson/friendly', 'old_nickname')]: + fetch_request.add(ax.AttrInfo(attr, alias=alias, required=True)) + openid_request.addExtension(fetch_request) + else: + openid_request.addExtension( + sreg.SRegRequest(optional=['email', 'fullname', 'nickname'])) + + # Request team info + teams_mapping_auto = getattr(settings, 'OPENID_LAUNCHPAD_TEAMS_MAPPING_AUTO', False) + teams_mapping_auto_blacklist = getattr(settings, 'OPENID_LAUNCHPAD_TEAMS_MAPPING_AUTO_BLACKLIST', []) + launchpad_teams = getattr(settings, 'OPENID_LAUNCHPAD_TEAMS_MAPPING', {}) + if teams_mapping_auto: + #ignore launchpad teams. use all django-groups + launchpad_teams = dict() + all_groups = Group.objects.exclude(name__in=teams_mapping_auto_blacklist) + for group in all_groups: + launchpad_teams[group.name] = group.name + + if launchpad_teams: + openid_request.addExtension(teams.TeamsRequest(launchpad_teams.keys())) + + # Construct the request completion URL, including the page we + # should redirect to. + return_to = request.build_absolute_uri(reverse(login_complete_view)) + if redirect_to: + if '?' in return_to: + return_to += '&' + else: + return_to += '?' + return_to += urllib.urlencode({redirect_field_name: redirect_to}) + + return render_openid_request(request, openid_request, return_to) + + +@csrf_exempt +def login_complete(request, redirect_field_name=REDIRECT_FIELD_NAME, + render_failure=default_render_failure): + redirect_to = request.REQUEST.get(redirect_field_name, '') + + openid_response = parse_openid_response(request) + if not openid_response: + return render_failure( + request, 'This is an OpenID relying party endpoint.') + + if openid_response.status == SUCCESS: + user = authenticate(openid_response=openid_response) + if user is not None: + if user.is_active: + auth_login(request, user) + return HttpResponseRedirect(sanitise_redirect_url(redirect_to)) + else: + return render_failure(request, 'Disabled account') + else: + return render_failure(request, 'Unknown user') + elif openid_response.status == FAILURE: + return render_failure( + request, 'OpenID authentication failed: %s' % + openid_response.message) + elif openid_response.status == CANCEL: + return render_failure(request, 'Authentication cancelled') + else: + assert False, ( + "Unknown OpenID response type: %r" % openid_response.status) + + +def logo(request): + return HttpResponse( + OPENID_LOGO_BASE_64.decode('base64'), mimetype='image/gif' + ) + +# Logo from http://openid.net/login-bg.gif +# Embedded here for convenience; you should serve this as a static file +OPENID_LOGO_BASE_64 = """ +R0lGODlhEAAQAMQAAO3t7eHh4srKyvz8/P5pDP9rENLS0v/28P/17tXV1dHEvPDw8M3Nzfn5+d3d +3f5jA97Syvnv6MfLzcfHx/1mCPx4Kc/S1Pf189C+tP+xgv/k1N3OxfHy9NLV1/39/f///yH5BAAA +AAAALAAAAAAQABAAAAVq4CeOZGme6KhlSDoexdO6H0IUR+otwUYRkMDCUwIYJhLFTyGZJACAwQcg +EAQ4kVuEE2AIGAOPQQAQwXCfS8KQGAwMjIYIUSi03B7iJ+AcnmclHg4TAh0QDzIpCw4WGBUZeikD +Fzk0lpcjIQA7 +""" diff --git a/3rdparty/django-openid-auth/example_consumer/__init__.py b/3rdparty/django-openid-auth/example_consumer/__init__.py new file mode 100644 index 00000000..956164e6 --- /dev/null +++ b/3rdparty/django-openid-auth/example_consumer/__init__.py @@ -0,0 +1,28 @@ +# django-openid-auth - OpenID integration for django.contrib.auth +# +# Copyright (C) 2007 Simon Willison +# Copyright (C) 2008-2010 Canonical Ltd. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. diff --git a/3rdparty/django-openid-auth/example_consumer/manage.py b/3rdparty/django-openid-auth/example_consumer/manage.py new file mode 100755 index 00000000..5e78ea97 --- /dev/null +++ b/3rdparty/django-openid-auth/example_consumer/manage.py @@ -0,0 +1,11 @@ +#!/usr/bin/env python +from django.core.management import execute_manager +try: + import settings # Assumed to be in the same directory. +except ImportError: + import sys + sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__) + sys.exit(1) + +if __name__ == "__main__": + execute_manager(settings) diff --git a/3rdparty/django-openid-auth/example_consumer/settings.py b/3rdparty/django-openid-auth/example_consumer/settings.py new file mode 100644 index 00000000..d23b1267 --- /dev/null +++ b/3rdparty/django-openid-auth/example_consumer/settings.py @@ -0,0 +1,133 @@ +# django-openid-auth - OpenID integration for django.contrib.auth +# +# Copyright (C) 2007 Simon Willison +# Copyright (C) 2008-2010 Canonical Ltd. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +# Django settings for example project. + +DEBUG = True +TEMPLATE_DEBUG = DEBUG + +ADMINS = ( + # ('Your Name', 'your_email@domain.com'), +) + +MANAGERS = ADMINS + +DATABASE_ENGINE = 'sqlite3' # 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'ado_mssql'. +DATABASE_NAME = 'sqlite.db' # Or path to database file if using sqlite3. +DATABASE_USER = '' # Not used with sqlite3. +DATABASE_PASSWORD = '' # Not used with sqlite3. +DATABASE_HOST = '' # Set to empty string for localhost. Not used with sqlite3. +DATABASE_PORT = '' # Set to empty string for default. Not used with sqlite3. + +# Local time zone for this installation. Choices can be found here: +# http://www.postgresql.org/docs/8.1/static/datetime-keywords.html#DATETIME-TIMEZONE-SET-TABLE +# although not all variations may be possible on all operating systems. +# If running in a Windows environment this must be set to the same as your +# system time zone. +TIME_ZONE = 'America/Chicago' + +# Language code for this installation. All choices can be found here: +# http://www.w3.org/TR/REC-html40/struct/dirlang.html#langcodes +# http://blogs.law.harvard.edu/tech/stories/storyReader$15 +LANGUAGE_CODE = 'en-us' + +SITE_ID = 1 + +# If you set this to False, Django will make some optimizations so as not +# to load the internationalization machinery. +USE_I18N = True + +# Absolute path to the directory that holds media. +# Example: "/home/media/media.lawrence.com/" +MEDIA_ROOT = '' + +# URL that handles the media served from MEDIA_ROOT. +# Example: "http://media.lawrence.com" +MEDIA_URL = '' + +# URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a +# trailing slash. +# Examples: "http://foo.com/media/", "/media/". +ADMIN_MEDIA_PREFIX = '/media/' + +# Make this unique, and don't share it with anybody. +SECRET_KEY = '34958734985734985734985798437' + +# List of callables that know how to import templates from various sources. +TEMPLATE_LOADERS = ( + 'django.template.loaders.filesystem.load_template_source', + 'django.template.loaders.app_directories.load_template_source', +# 'django.template.loaders.eggs.load_template_source', +) + +MIDDLEWARE_CLASSES = ( + 'django.middleware.common.CommonMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', +) + +ROOT_URLCONF = 'example_consumer.urls' + +TEMPLATE_DIRS = ( + # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates". + # Always use forward slashes, even on Windows. + # Don't forget to use absolute paths, not relative paths. +) + +INSTALLED_APPS = ( + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.admin', + 'django_openid_auth', +) + +AUTHENTICATION_BACKENDS = ( + 'django_openid_auth.auth.OpenIDBackend', + 'django.contrib.auth.backends.ModelBackend', +) + +# Should users be created when new OpenIDs are used to log in? +OPENID_CREATE_USERS = True + +# When logging in again, should we overwrite user details based on +# data received via Simple Registration? +OPENID_UPDATE_DETAILS_FROM_SREG = True + +# If set, always use this as the identity URL rather than asking the +# user. This only makes sense if it is a server URL. +OPENID_SSO_SERVER_URL = 'https://login.launchpad.net/' + +# Tell django.contrib.auth to use the OpenID signin URLs. +LOGIN_URL = '/openid/login/' +LOGIN_REDIRECT_URL = '/' + +# Should django_auth_openid be used to sign into the admin interface? +OPENID_USE_AS_ADMIN_LOGIN = False diff --git a/3rdparty/django-openid-auth/example_consumer/urls.py b/3rdparty/django-openid-auth/example_consumer/urls.py new file mode 100644 index 00000000..74d2ec3a --- /dev/null +++ b/3rdparty/django-openid-auth/example_consumer/urls.py @@ -0,0 +1,45 @@ +# django-openid-auth - OpenID integration for django.contrib.auth +# +# Copyright (C) 2007 Simon Willison +# Copyright (C) 2008-2010 Canonical Ltd. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +from django.conf.urls.defaults import * +from django.contrib import admin + +import views + + +admin.autodiscover() + +urlpatterns = patterns('', + (r'^$', views.index), + (r'^openid/', include('django_openid_auth.urls')), + (r'^logout/$', 'django.contrib.auth.views.logout'), + (r'^private/$', views.require_authentication), + + (r'^admin/(.*)', admin.site.root), +) diff --git a/3rdparty/django-openid-auth/example_consumer/views.py b/3rdparty/django-openid-auth/example_consumer/views.py new file mode 100644 index 00000000..3e8c3ed5 --- /dev/null +++ b/3rdparty/django-openid-auth/example_consumer/views.py @@ -0,0 +1,57 @@ +# django-openid-auth - OpenID integration for django.contrib.auth +# +# Copyright (C) 2007 Simon Willison +# Copyright (C) 2008-2010 Canonical Ltd. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +from django.contrib.auth.decorators import login_required +from django.http import HttpResponse +from django.utils.html import escape + + +def index(request): + s = ['

'] + if request.user.is_authenticated(): + s.append('You are signed in as %s (%s)' % ( + escape(request.user.username), + escape(request.user.get_full_name()))) + s.append(' | Sign out') + else: + s.append('Sign in with OpenID') + + s.append('

') + + s.append('

This requires authentication

') + return HttpResponse('\n'.join(s)) + + +def next_works(request): + return HttpResponse('?next= bit works. Home') + + +@login_required +def require_authentication(request): + return HttpResponse('This page requires authentication') diff --git a/3rdparty/django-openid-auth/openid.html b/3rdparty/django-openid-auth/openid.html new file mode 100644 index 00000000..cd8004f9 --- /dev/null +++ b/3rdparty/django-openid-auth/openid.html @@ -0,0 +1,150 @@ + + +OpenID in Django + +

OpenID in Django

+

The django_openidconsumer package contains all of the code needed to set up +your Django application as an OpenID consumer. You can use it to allow OpenID +users to sign in to your site without having to create a new username and +password.

+
+

Overview

+

The OpenID consumer system consists of:

+ +
+
+

Dependencies

+

django_openidconsumer uses the python-openid library, which must be +installed separately somewhere on the Python path. You should install the 1.2.0 +“combo” package which includes the yadis and urljr libraries.

+

The package also depends on the availability of Django’s session support.

+
+
+

Installation

+

Having ensured that both the python-openid library and the django_openidconsumer package are available on your Python path, you can +add OpenID consumer support to an application by doing the following:

+
    +
  1. Put django_openidconsumer in your INSTALLED_APPS setting.

    +
  2. +
  3. Run the command manage.py syncdb to create the necessary tables.

    +
  4. +
  5. Add django_openidconsumer.middleware.OpenIDMiddleware to your list +of MIDDLEWARE_CLASSES, somewhere after the Session middleware.

    +
  6. +
  7. Add the following views to your urlconf:

    +
    +(r'^openid/$', 'django_openidconsumer.views.begin'),
    +(r'^openid/complete/$', 'django_openidconsumer.views.complete'),
    +(r'^openid/signout/$', 'django_openidconsumer.views.signout'),
    +
    +
  8. +
+

You will then be able to browse to example.com/openid/ and sign in using +an OpenID.

+
+
+

Using the OpenID middleware

+

With the Middleware installed, your views will have access to the user’s OpenID +as the request.openid property. This will be None if the user has not +yet authenticated; otherwise it will be a django_openidconsumer.util.OpenID +instance.

+

If you want the user’s OpenID as a string, call the str() builtin on the +OpenID instance:

+
+def example_view(request):
+    if request.openid:
+        return HttpResponse("OpenID is %s" % escape(str(request.openid)))
+    else:
+        return HttpResponse("No OpenID")
+
+

Users can sign in with more than one OpenID. This is supported by the +request.openids property, which is a list of OpenID objects in the order +in which they were authenticated. request.openid merely returns the last +item in this list.

+
+
+

Using simple registration

+

Simple registration (or sreg) is an extension to the OpenID specification +that allows you to request extra details about a user from their OpenID +provider. It is frequently used to pre-populate registration forms with +information such as the user’s name, e-mail address or date of birth.

+

Be aware that not all OpenID providers support sreg, and there is no guarantee +that the information you have requested will be returned. Simple registration +should be used as a convenience for your users rather than as a required step in +your authentication process.

+

Available simple registration fields are nickname, email, fullname, +dob, gender, postcode, country, language and timezone. +Full details are available in the spec.

+

To request this information, pass the fields that you wish to retrieve as an +additional sreg argument to the django_openidconsumer.views.begin view:

+
+(r'^openid/$', 'django_openidconsumer.views.begin', {
+    'sreg': 'email,nickname'
+}),
+
+

Any simple registration fields that are returned will be available in a +dictionary as the sreg property of the OpenID object:

+
+def example_sreg(request):
+    if request.openid and request.openid.sreg.has_key('email'):
+        return HttpResponse("Your e-mail address is: %s" % escape(
+            request.openid.sreg['email']
+        ))
+    else:
+        return HttpResponse("No e-mail address")
+
+
+
+

Customisation

+

django_openidconsumer uses two templates:

+
+
openid_signin.html
+
The form presented to the user when they sign in.
+
openid_failure.html
+
The template used to display an error message when something goes wrong.
+
+

You can over-ride the default templates by creating templates of the same name +and placing them somewhere on your template path. You can find the example +templates in the django_openidconsumer/templates directory.

+

The OpenID specification strongly recommends that any OpenID registration form +has a name attribute of openid_url to aid browser autocompletion, and +displays the OpenID logo inline in the form field using the following CSS:

+
+input.openid {
+  background: url(/path/to/login-bg.gif) no-repeat;
+  background-position: 0 50%;
+  padding-left: 16px;
+}
+
+

By default, the package expects the django_openidconsumer.views.complete +view to be located at /openid/complete/. This is the view that the OpenID +provider will redirect the user to after they have authenticated. If you want to +put it somewhere else you can either pass an extra redirect_to argument to +django_openidconsumer.views.begin or add an OPENID_REDIRECT_TO setting +to settings.py.

+

You can pass a ?next= query string argument containing a relative URL to +the begin view to control where the user will be redirected to having +returned to your site. You can also set the default redirection location +using the OPENID_REDIRECT_NEXT setting; if you do set set a default the user +will be redirected to your homepage.

+
+
+

i-names

+

i-names are part of the OpenID 2.0 specification, which is currently being +developed. They are supported by the python-openid library, and hence are also +supported by django_openidconsumer. You can tell if an OpenID is an i-name +by checking the request.openid.is_iname property.

+

If you wish to disable i-name support, you can do so by adding the following to +your settings.py:

+
+OPENID_DISALLOW_INAMES = True
+
+
+ + \ No newline at end of file diff --git a/3rdparty/django-openid-auth/openid.txt b/3rdparty/django-openid-auth/openid.txt new file mode 100644 index 00000000..043cdfe4 --- /dev/null +++ b/3rdparty/django-openid-auth/openid.txt @@ -0,0 +1,165 @@ +================ +OpenID in Django +================ + +The ``django_openidconsumer`` package contains all of the code needed to set up +your Django application as an OpenID consumer. You can use it to allow OpenID +users to sign in to your site without having to create a new username and +password. + +Overview +======== + +The OpenID consumer system consists of: + + * Views for you to hook in to your application. + * Database models implementing the persistence layer of an OpenID consumer. + * Middleware that makes ``request.openid`` and ``request.openids`` + properties available to your application views. + +Dependencies +============ + +``django_openidconsumer`` uses the `python-openid library`_, which must be +installed separately somewhere on the Python path. You should install the 1.2.0 +"combo" package which includes the ``yadis`` and ``urljr`` libraries. + +The package also depends on the availability of Django's `session support`_. + +.. _python-openid library: http://www.openidenabled.com/openid/libraries/python/ +.. _session support: http://www.djangoproject.com/documentation/sessions/ + +Installation +============ + +Having ensured that both the ``python-openid`` library and the ``django_openidconsumer`` package are available on your Python path, you can +add OpenID consumer support to an application by doing the following: + + 1. Put ``django_openidconsumer`` in your ``INSTALLED_APPS`` setting. + 2. Run the command ``manage.py syncdb`` to create the necessary tables. + 3. Add ``django_openidconsumer.middleware.OpenIDMiddleware`` to your list + of ``MIDDLEWARE_CLASSES``, somewhere after the Session middleware. + 4. Add the following views to your urlconf:: + + (r'^openid/$', 'django_openidconsumer.views.begin'), + (r'^openid/complete/$', 'django_openidconsumer.views.complete'), + (r'^openid/signout/$', 'django_openidconsumer.views.signout'), + +You will then be able to browse to ``example.com/openid/`` and sign in using +an OpenID. + +Using the OpenID middleware +=========================== + +With the Middleware installed, your views will have access to the user's OpenID +as the ``request.openid`` property. This will be ``None`` if the user has not +yet authenticated; otherwise it will be a ``django_openidconsumer.util.OpenID`` +instance. + +If you want the user's OpenID as a string, call the ``str()`` builtin on the +OpenID instance:: + + def example_view(request): + if request.openid: + return HttpResponse("OpenID is %s" % escape(str(request.openid))) + else: + return HttpResponse("No OpenID") + +Users can sign in with more than one OpenID. This is supported by the +``request.openids`` property, which is a list of ``OpenID`` objects in the order +in which they were authenticated. ``request.openid`` merely returns the last +item in this list. + +Using simple registration +========================= + +Simple registration (or `sreg`_) is an extension to the OpenID specification +that allows you to request extra details about a user from their OpenID +provider. It is frequently used to pre-populate registration forms with +information such as the user's name, e-mail address or date of birth. + +.. _sreg: http://openid.net/specs/openid-simple-registration-extension-1_0.html + +Be aware that not all OpenID providers support sreg, and there is no guarantee +that the information you have requested will be returned. Simple registration +should be used as a convenience for your users rather than as a required step in +your authentication process. + +Available simple registration fields are ``nickname``, ``email``, ``fullname``, +``dob``, ``gender``, ``postcode``, ``country``, ``language`` and ``timezone``. +Full details are available in the `spec`_. + +.. _spec: http://openid.net/specs/openid-simple-registration-extension-1_0.html + +To request this information, pass the fields that you wish to retrieve as an +additional ``sreg`` argument to the ``django_openidconsumer.views.begin`` view:: + + (r'^openid/$', 'django_openidconsumer.views.begin', { + 'sreg': 'email,nickname' + }), + +Any simple registration fields that are returned will be available in a +dictionary as the ``sreg`` property of the OpenID object:: + + def example_sreg(request): + if request.openid and request.openid.sreg.has_key('email'): + return HttpResponse("Your e-mail address is: %s" % escape( + request.openid.sreg['email'] + )) + else: + return HttpResponse("No e-mail address") + +Customisation +============= + +``django_openidconsumer`` uses two templates: + +``openid_signin.html`` + The form presented to the user when they sign in. + +``openid_failure.html`` + The template used to display an error message when something goes wrong. + +You can over-ride the default templates by creating templates of the same name +and placing them somewhere on your template path. You can find the example +templates in the ``django_openidconsumer/templates`` directory. + +The OpenID specification strongly recommends that any OpenID registration form +has a ``name`` attribute of ``openid_url`` to aid browser autocompletion, and +displays the `OpenID logo`_ inline in the form field using the following CSS:: + + input.openid { + background: url(/path/to/login-bg.gif) no-repeat; + background-position: 0 50%; + padding-left: 16px; + } + +.. _OpenID logo: http://openid.net/login-bg.gif + +By default, the package expects the ``django_openidconsumer.views.complete`` +view to be located at ``/openid/complete/``. This is the view that the OpenID +provider will redirect the user to after they have authenticated. If you want to +put it somewhere else you can either pass an extra ``redirect_to`` argument to +``django_openidconsumer.views.begin`` or add an ``OPENID_REDIRECT_TO`` setting +to ``settings.py``. + +You can pass a ``?next=`` query string argument containing a relative URL to +the ``begin`` view to control where the user will be redirected to having +returned to your site. You can also set the default redirection location +using the ``OPENID_REDIRECT_NEXT`` setting; if you do set set a default the user +will be redirected to your homepage. + +i-names +======= + +`i-names`_ are part of the OpenID 2.0 specification, which is currently being +developed. They are supported by the python-openid library, and hence are also +supported by ``django_openidconsumer``. You can tell if an OpenID is an i-name +by checking the ``request.openid.is_iname`` property. + +.. _i-names: http://www.inames.net/ + +If you wish to disable i-name support, you can do so by adding the following to +your ``settings.py``:: + + OPENID_DISALLOW_INAMES = True diff --git a/3rdparty/django-openid-auth/setup.py b/3rdparty/django-openid-auth/setup.py new file mode 100644 index 00000000..03d48200 --- /dev/null +++ b/3rdparty/django-openid-auth/setup.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python +# django-openid-auth - OpenID integration for django.contrib.auth +# +# Copyright (C) 2009-2010 Canonical Ltd. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +"""OpenID integration for django.contrib.auth + +A library that can be used to add OpenID support to Django applications. +The library integrates with Django's built in authentication system, so +most applications require minimal changes to support OpenID llogin. The +library also includes the following features: + * Basic user details are transferred from the OpenID server via the + Simple Registration extension or Attribute Exchange extension. + * can be configured to use a fixed OpenID server URL, for use in SSO. + * supports the launchpad.net teams extension to get team membership + info. +""" + +from distutils.core import setup + + +description, long_description = __doc__.split('\n\n', 1) +VERSION = '0.3' + +setup( + name='django-openid-auth', + version=VERSION, + author='Canonical Ltd', + description=description, + long_description=long_description, + license='BSD', + platforms=['any'], + url='https://launchpad.net/django-openid-auth', + download_url=('http://launchpad.net/django-openid-auth/trunk/%s/+download' + '/django-openid-auth-%s.tar.gz' % (VERSION, VERSION)), + classifiers=[ + 'Development Status :: 4 - Beta', + 'Environment :: Web Environment', + 'Framework :: Django', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: BSD License', + 'Operating System :: OS Independent', + 'Programming Language :: Python', + 'Topic :: Software Development :: Libraries :: Python Modules' + ], + packages=[ + 'django_openid_auth', + 'django_openid_auth.management', + 'django_openid_auth.management.commands', + 'django_openid_auth.tests', + ], + package_data={ + 'django_openid_auth': ['templates/openid/*.html'], + }, + provides=['django_openid_auth'], + requires=['django (>=1.2)', 'openid (>=2.2.0)'], + ) diff --git a/Dockerfile b/Dockerfile index ab3f7011..a8989680 100644 --- a/Dockerfile +++ b/Dockerfile @@ -24,6 +24,7 @@ RUN apt-get update && apt-get install -y \ memcached \ wget COPY lernanta/requirements/prod.txt /opt/app/requirements.txt +COPY 3rdparty/ /opt/app/3rdparty/ RUN virtualenv /opt/django-venv \ && /opt/django-venv/bin/pip install -r /opt/app/requirements.txt COPY lernanta/ /opt/app/lernanta/ diff --git a/README.rst b/README.rst index 272eb293..551881fc 100644 --- a/README.rst +++ b/README.rst @@ -24,3 +24,8 @@ Or if you enjoy linux command line hacking, check out our `install docs`_ on our .. _environment setup script: https://github.com/p2pu/lernanta-dev-env .. _install docs: https://github.com/p2pu/lernanta/wiki/Lernanta%27s-Setup-Install + + +Notes +----- +docker-compose run --rm lernanta /opt/django-venv/bin/python /opt/app/lernanta/manage.py syncdb diff --git a/lernanta/requirements/prod.txt b/lernanta/requirements/prod.txt index 2ef8c131..960f5daf 100644 --- a/lernanta/requirements/prod.txt +++ b/lernanta/requirements/prod.txt @@ -1,6 +1,6 @@ BeautifulSoup==3.2.0 Django==1.3.1 --e git://github.com/embedly/embedly-python.git@b8325fc49396e3b0d10f65847c6cc9fb23bd9482#egg=Embedly-dev +-e git+https://github.com/embedly/embedly-python.git@b8325fc49396e3b0d10f65847c6cc9fb23bd9482#egg=Embedly-dev M2Crypto==0.25.1 Markdown==2.1.1 MySQL-python==1.2.3 @@ -11,17 +11,17 @@ anyjson==0.3.3 argparse==1.2.1 bleach==1.1.5 celery==2.4.6 --e git://github.com/jsocol/commonware.git@b5544185b2d24adc1eb512735990752400ce9cbd#egg=commonware-dev --e git://github.com/jbalogh/django-cache-machine.git@fd9c7efbd14dfb78f278d7b270cfe7a9da1b79b7#egg=django_cache_machine-dev +-e git+https://github.com/jsocol/commonware.git@b5544185b2d24adc1eb512735990752400ce9cbd#egg=commonware-dev +-e git+https://github.com/jbalogh/django-cache-machine.git@fd9c7efbd14dfb78f278d7b270cfe7a9da1b79b7#egg=django_cache_machine-dev django-celery==2.4.2 --e git://github.com/shaunsephton/django-ckeditor.git@39a4059483c057a00455baa895f2a263baac2161#egg=django_ckeditor-dev +-e git+https://github.com/shaunsephton/django-ckeditor.git@39a4059483c057a00455baa895f2a263baac2161#egg=django_ckeditor-dev django-maintenancemode==0.9.3.1 django-messages==0.4.4 --e git://github.com/zuzelvp/django-obi.git@2680327dca0eecd5790653a50eed12e4c9c8c7f9#egg=django_obi-dev --e git://github.com/paulosman/django-openid-auth.git@ae650cca027e69488070073d9b911ec82ae86be2#egg=django_openid_auth-dev +-e git+https://github.com/zuzelvp/django-obi.git@2680327dca0eecd5790653a50eed12e4c9c8c7f9#egg=django_obi-dev +-e file:///opt/app/3rdparty/django-openid-auth#egg=django_openid_auth-dev django-picklefield==0.3.0 django-push==0.4 --e git://github.com/mozilla/django-recaptcha.git@b3ce0daadfff7735d2a0865b8419711748c9190a#egg=django_recaptcha-dev +-e git+https://github.com/mozilla/django-recaptcha.git@b3ce0daadfff7735d2a0865b8419711748c9190a#egg=django_recaptcha-dev django-robots==0.8.1 django-taggit==0.9.3 django-tastypie==0.9.11 @@ -34,16 +34,16 @@ kombu==2.5.4 lxml==2.3.2 mimeparse==0.1.3 oauth==1.0.1 --e git://github.com/hiidef/oauth2app.git@f8205e2146477224a666c03bf115ddf3ff7610ff#egg=oauth2app-dev +-e git+https://github.com/hiidef/oauth2app.git@f8205e2146477224a666c03bf115ddf3ff7610ff#egg=oauth2app-dev python-memcached==1.48 python-openid==2.2.5 --e git://github.com/paulosman/python-xrd@9a62985ec31e90afcae89c2ce0450eec9f5fca07#egg=python_xrd-dev +-e git+https://github.com/paulosman/python-xrd@9a62985ec31e90afcae89c2ce0450eec9f5fca07#egg=python_xrd-dev recaptcha-client==1.0.6 requests==0.13.5 simplejson==2.6.0 six==1.2.0 tender-multipass==0.2.1 --e git://github.com/jdunck/python-unicodecsv.git@c5d8ae68758dd48d8a776214080caa88bc036644#egg=unicodecsv-dev --e git://github.com/paulosman/django-wellknown.git@3cc3cfc1217aed065d17875dd832cd41110cd4ba#egg=wellknown-dev +-e git+https://github.com/jdunck/python-unicodecsv.git@c5d8ae68758dd48d8a776214080caa88bc036644#egg=unicodecsv-dev +-e git+https://github.com/paulosman/django-wellknown.git@3cc3cfc1217aed065d17875dd832cd41110cd4ba#egg=wellknown-dev wsgiref==0.1.2 gunicorn==19.7.1