Commit 8b210641 by bmedx

Update repo from upstream revision 128

- Previous pull was revision 115
- Diff of versions: http://bazaar.launchpad.net/~ubuntuone-pqm-team/django-openid-auth/trunk/revision/128?remember=115&compare_revid=115
- Should get us Py3 and Django up to 1.10 support
parent 86b822c2
# Created by .ignore support plugin (hsz.mobi)
### Python template
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# Distribution / packaging
.Python
env/
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*,cover
.hypothesis/
# Django stuff:
*.log
local_settings.py
# dotenv
.env
# virtualenv
.venv
venv/
ENV/
.gitignore
.idea/
django_openid_auth.egg-info/
......@@ -26,3 +26,6 @@
# 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 sys
PY3 = sys.version_info.major >= 3
......@@ -29,8 +29,11 @@
from __future__ import unicode_literals
from urllib import urlencode
from urlparse import parse_qsl, urlparse
try:
from urllib.parse import parse_qsl, urlencode, urlparse
except ImportError:
from urllib import urlencode
from urlparse import parse_qsl, urlparse
from django.conf import settings
from django.contrib import admin
......@@ -50,6 +53,7 @@ class NonceAdmin(admin.ModelAdmin):
self.message_user(request, "%d expired nonces removed" % count)
cleanup_nonces.short_description = "Clean up expired nonces"
admin.site.register(Nonce, NonceAdmin)
......@@ -65,6 +69,7 @@ class AssociationAdmin(admin.ModelAdmin):
self.message_user(request, "%d expired associations removed" % count)
cleanup_associations.short_description = "Clean up expired associations"
admin.site.register(Association, AssociationAdmin)
......@@ -73,6 +78,7 @@ class UserOpenIDAdmin(admin.ModelAdmin):
list_display = ('user', 'claimed_id')
search_fields = ('claimed_id',)
admin.site.register(UserOpenID, UserOpenIDAdmin)
......
......@@ -30,13 +30,12 @@
from __future__ import unicode_literals
__metaclass__ = type
import re
from django.conf import settings
from django.contrib.auth import get_user_model
from django.contrib.auth.models import Group, Permission
from django.core.exceptions import ImproperlyConfigured
from openid.consumer.consumer import SUCCESS
from openid.extensions import ax, sreg, pape
......@@ -49,12 +48,51 @@ from django_openid_auth.exceptions import (
MissingPhysicalMultiFactor,
RequiredAttributeNotReturned,
)
from django_openid_auth.signals import openid_duplicate_username
User = get_user_model()
class OpenIDBackend:
def get_user_group_model():
"""Returns the model used for mapping users to groups."""
user_group_model_name = getattr(settings, 'AUTH_USER_GROUP_MODEL', None)
if user_group_model_name is None:
return User.groups.through
else:
try:
# django.apps available starting from django 1.7
from django.apps import apps
get_model = apps.get_model
args = (user_group_model_name,)
except ImportError:
# if we can't import, then it must be django 1.6, still using
# the old django.db.models.loading code
from django.db.models.loading import get_model
app_label, model_name = user_group_model_name.split('.', 1)
args = (app_label, model_name)
try:
model = get_model(*args)
if model is None:
# in django 1.6 referring to a non-installed app will
# return None for get_model, but in 1.7 onwards it will
# raise a LookupError exception.
raise LookupError()
return model
except ValueError:
raise ImproperlyConfigured(
"AUTH_USER_GROUP_MODEL must be of the form "
"'app_label.model_name'")
except LookupError:
raise ImproperlyConfigured(
"AUTH_USER_GROUP_MODEL refers to model '%s' that has not been "
"installed" % user_group_model_name)
UserGroup = get_user_group_model()
class OpenIDBackend(object):
"""A django.contrib.auth backend that authenticates the user based on
an OpenID response."""
......@@ -116,8 +154,9 @@ class OpenIDBackend:
teams_mapping = self.get_teams_mapping()
groups_required = [group for team, group in teams_mapping.items()
if team in teams_required]
user_groups = UserGroup.objects.filter(user=user)
matches = set(groups_required).intersection(
user.groups.values_list('name', flat=True))
user_groups.values_list('group__name', flat=True))
if not matches:
name = 'OPENID_EMAIL_WHITELIST_REGEXP_LIST'
whitelist_regexp_list = getattr(settings, name, [])
......@@ -194,28 +233,19 @@ class OpenIDBackend:
return suggestion
return 'openiduser'
def _get_available_username(self, nickname, identity_url):
# If we're being strict about usernames, throw an error if we didn't
# get one back from the provider
if getattr(settings, 'OPENID_STRICT_USERNAMES', False):
if nickname is None or nickname == '':
raise MissingUsernameViolation()
def _get_available_username_for_nickname(self, nickname, identity_url):
# If we don't have a nickname, and we're not being strict, use a
# default
nickname = nickname or 'openiduser'
# See if we already have this nickname assigned to a username
try:
User.objects.get(username__exact=nickname)
except User.DoesNotExist:
# No conflict, we can use this nickname
if not User.objects.filter(username=nickname).exists():
return nickname
# Check if we already have nickname+i for this identity_url
try:
user_openid = UserOpenID.objects.get(
claimed_id__exact=identity_url,
claimed_id=identity_url,
user__username__startswith=nickname)
# No exception means we have an existing user for this identity
# that starts with this nickname.
......@@ -239,28 +269,48 @@ class OpenIDBackend:
# No user associated with this identity_url
pass
if getattr(settings, 'OPENID_STRICT_USERNAMES', False):
if User.objects.filter(username__exact=nickname).count() > 0:
raise DuplicateUsernameViolation(
"The username (%s) with which you tried to log in is "
"already in use for a different account." % nickname)
# Pick a username for the user based on their nickname,
# checking for conflicts. Start with number of existing users who's
# username starts with this nickname to avoid having to iterate over
# all of the existing ones.
i = User.objects.filter(username__startswith=nickname).count() + 1
while True:
username = nickname
if i > 1:
username += str(i)
try:
User.objects.get(username__exact=username)
except User.DoesNotExist:
break
username = nickname
while User.objects.filter(username=username).exists():
username = nickname + str(i)
i += 1
return username
def _ensure_available_username(self, nickname, identity_url):
if not nickname:
raise MissingUsernameViolation()
# As long as the `QuerySet` does not get evaluated, no
# caching should be involved in our multiple `exists()`
# calls. See docs for details: http://bit.ly/2aYCmkw
user_with_same_username = User.objects.exclude(
useropenid__claimed_id=identity_url
).filter(username=nickname)
if user_with_same_username.exists():
# Notify any listeners that a duplicated username was
# found and give the opportunity to handle conflict.
openid_duplicate_username.send(sender=User, username=nickname)
# Check for conflicts again as the signal could have handled it.
if user_with_same_username.exists():
raise DuplicateUsernameViolation(
"The username (%s) with which you tried to log in is "
"already in use for a different account." % nickname)
def _get_available_username(self, nickname, identity_url):
if getattr(settings, 'OPENID_STRICT_USERNAMES', False):
self._ensure_available_username(nickname, identity_url)
else:
nickname = self._get_available_username_for_nickname(
nickname, identity_url)
return nickname
def create_user_from_openid(self, openid_response):
details = self._extract_user_details(openid_response)
required_attrs = getattr(settings, 'OPENID_SREG_REQUIRED_FIELDS', [])
......@@ -357,13 +407,17 @@ class OpenIDBackend:
mapping = [
teams_mapping[lp_team] for lp_team in teams_response.is_member
if lp_team in teams_mapping]
user_groups = UserGroup.objects.filter(user=user)
matching_groups = user_groups.filter(
group__name__in=teams_mapping.values())
current_groups = set(
user.groups.filter(name__in=teams_mapping.values()))
user_group.group for user_group in matching_groups)
desired_groups = set(Group.objects.filter(name__in=mapping))
for group in current_groups - desired_groups:
user.groups.remove(group)
for group in desired_groups - current_groups:
user.groups.add(group)
groups_to_remove = current_groups - desired_groups
groups_to_add = desired_groups - current_groups
user_groups.filter(group__in=groups_to_remove).delete()
for group in groups_to_add:
UserGroup.objects.create(user=user, group=group)
def update_staff_status_from_teams(self, user, teams_response):
if not hasattr(settings, 'OPENID_LAUNCHPAD_STAFF_TEAMS'):
......
......@@ -38,6 +38,8 @@ from django.conf import settings
from openid.yadis import xri
from django_openid_auth import PY3
def teams_new_unicode(self):
"""
......@@ -52,8 +54,13 @@ def teams_new_unicode(self):
else:
return name
Group.unicode_before_teams = Group.__unicode__
Group.__unicode__ = teams_new_unicode
if PY3:
Group.unicode_before_teams = Group.__str__
Group.__str__ = teams_new_unicode
else:
Group.unicode_before_teams = Group.__unicode__
Group.__unicode__ = teams_new_unicode
class UserChangeFormWithTeamRestriction(UserChangeForm):
......@@ -72,6 +79,7 @@ class UserChangeFormWithTeamRestriction(UserChangeForm):
"You cannot assign it manually." % group.name)
return data
UserAdmin.form = UserChangeFormWithTeamRestriction
......
......@@ -34,3 +34,4 @@ import django.dispatch
openid_login_complete = django.dispatch.Signal(providing_args=[
'request', 'openid_response'])
openid_duplicate_username = django.dispatch.Signal(providing_args=['username'])
......@@ -36,6 +36,7 @@ from openid.association import Association as OIDAssociation
from openid.store.interface import OpenIDStore
from openid.store.nonce import SKEW
from django_openid_auth import PY3
from django_openid_auth.models import Association, Nonce
......@@ -75,10 +76,15 @@ class DjangoOpenIDStore(OpenIDStore):
expired = []
for assoc in assocs:
association = OIDAssociation(
assoc.handle, base64.decodestring(assoc.secret), assoc.issued,
assoc.lifetime, assoc.assoc_type
assoc.handle,
base64.decodestring(assoc.secret.encode('utf-8')),
assoc.issued, assoc.lifetime, assoc.assoc_type
)
if association.getExpiresIn() == 0:
if PY3:
expires_in = association.expiresIn
else:
expires_in = association.getExpiresIn()
if expires_in == 0:
expired.append(assoc)
else:
associations.append((association.issued, association))
......
......@@ -72,6 +72,7 @@ from openid.message import (
registerNamespaceAlias,
NamespaceAliasRegistrationError,
)
from six import string_types
__all__ = [
'TeamsRequest',
......@@ -84,7 +85,7 @@ ns_uri = 'http://ns.launchpad.net/2007/openid-teams'
try:
registerNamespaceAlias(ns_uri, 'lp')
except NamespaceAliasRegistrationError, e:
except NamespaceAliasRegistrationError as e:
oidutil.log(
'registerNamespaceAlias(%r, %r) failed: %s' % (ns_uri, 'lp', str(e)))
......@@ -139,7 +140,7 @@ def getTeamsNS(message):
# There is no alias, so try to add one. (OpenID version 1)
try:
message.namespaces.addAlias(ns_uri, 'lp')
except KeyError, why:
except KeyError as why:
# An alias for the string 'lp' already exists, but it's
# defined for something other than Launchpad teams
raise TeamsNamespaceError(why[0])
......@@ -287,7 +288,7 @@ class TeamsRequest(Extension):
@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):
if isinstance(query_membership, string_types):
raise TypeError('Teams should be passed as a list of '
'strings (not %r)' % (type(query_membership),))
......
{% load i18n %}
{% load url from future %}
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
......
from django.conf import settings
from django.contrib.auth.models import Group
from django.db import models
class UserGroup(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL)
group = models.ForeignKey(Group)
......@@ -28,6 +28,7 @@
from __future__ import unicode_literals
import base64
import time
from django.test import TestCase
......@@ -52,7 +53,8 @@ class OpenIDStoreTests(TestCase):
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.secret, base64.encodestring(b'secret').decode('utf-8'))
self.assertEquals(dbassoc.issued, 42)
self.assertEquals(dbassoc.lifetime, 600)
self.assertEquals(dbassoc.assoc_type, 'HMAC-SHA1')
......@@ -66,7 +68,8 @@ class OpenIDStoreTests(TestCase):
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.secret, base64.encodestring(b'secret2').decode('utf-8'))
self.assertEqual(dbassoc.issued, 420)
self.assertEqual(dbassoc.lifetime, 900)
self.assertEqual(dbassoc.assoc_type, 'HMAC-SHA256')
......@@ -80,7 +83,7 @@ class OpenIDStoreTests(TestCase):
self.assertTrue(isinstance(assoc, OIDAssociation))
self.assertEquals(assoc.handle, 'handle')
self.assertEquals(assoc.secret, 'secret')
self.assertEquals(assoc.secret, b'secret')
self.assertEquals(assoc.issued, timestamp)
self.assertEquals(assoc.lifetime, 600)
self.assertEquals(assoc.assoc_type, 'HMAC-SHA1')
......
......@@ -28,7 +28,7 @@
from __future__ import unicode_literals
from django.conf.urls import patterns, include
from django.conf.urls import include, url
from django.http import HttpResponse
......@@ -36,8 +36,7 @@ def get_user(request):
return HttpResponse(request.user.username)
urlpatterns = patterns(
'',
(r'^getuser/$', get_user),
(r'^openid/', include('django_openid_auth.urls')),
)
urlpatterns = [
url(r'^getuser/$', get_user),
url(r'^openid/', include('django_openid_auth.urls')),
]
......@@ -29,11 +29,17 @@
from __future__ import unicode_literals
from django.conf.urls import patterns, url
from django.conf.urls import url
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'),
from django_openid_auth.views import (
login_begin,
login_complete,
logo,
)
urlpatterns = [
url(r'^login/$', login_begin, name='openid-login'),
url(r'^complete/$', login_complete, name='openid-complete'),
url(r'^logo.gif$', logo, name='openid-logo'),
]
......@@ -30,8 +30,11 @@
from __future__ import unicode_literals
import re
import urllib
from urlparse import urlsplit
try:
from urllib.parse import urlencode, urlsplit
except ImportError:
from urllib import urlencode
from urlparse import urlsplit
from django.conf import settings
from django.contrib.auth import (
......@@ -39,6 +42,7 @@ from django.contrib.auth import (
from django.contrib.auth.models import Group
from django.core.urlresolvers import reverse
from django.http import HttpResponse, HttpResponseRedirect
from django.http.request import QueryDict
from django.shortcuts import render_to_response
from django.template import RequestContext
from django.template.loader import render_to_string
......@@ -129,22 +133,27 @@ def default_render_failure(request, message, status=403,
template_name='openid/failure.html',
exception=None):
"""Render an error page to the user."""
data = render_to_string(
template_name, dict(message=message, exception=exception),
context_instance=RequestContext(request))
context = RequestContext(request)
context.update(dict(message=message, exception=exception))
data = render_to_string(template_name, context)
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)
data = get_request_data(request)
return consumer.complete(data, current_url)
def get_request_data(request):
# simulate old request.REQUEST for backwards compatibility
data = QueryDict(query_string=None, mutable=True)
data.update(request.GET)
data.update(request.POST)
return data
def login_begin(request, template_name='openid/login.html',
......@@ -153,7 +162,8 @@ def login_begin(request, template_name='openid/login.html',
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, '')
data = get_request_data(request)
redirect_to = data.get(redirect_field_name, '')
# Get the OpenID URL to try. First see if we've been configured
# to use a fixed server URL.
......@@ -169,10 +179,12 @@ def login_begin(request, template_name='openid/login.html',
# Invalid or no form data:
if openid_url is None:
context = {'form': login_form, redirect_field_name: redirect_to}
return render_to_response(
template_name, context,
context_instance=RequestContext(request))
context = RequestContext(request)
context.update({
'form': login_form,
redirect_field_name: redirect_to,
})
return render_to_response(template_name, context)
consumer = make_consumer(request)
try:
......@@ -268,7 +280,7 @@ def login_begin(request, template_name='openid/login.html',
# Django gives us Unicode, which is great. We must encode URI.
# urllib enforces str. We can't trust anything about the default
# encoding inside str(foo) , so we must explicitly make foo a str.
return_to += urllib.urlencode(
return_to += urlencode(
{redirect_field_name: redirect_to.encode("UTF-8")})
return render_openid_request(request, openid_request, return_to)
......@@ -277,7 +289,8 @@ def login_begin(request, template_name='openid/login.html',
@csrf_exempt
def login_complete(request, redirect_field_name=REDIRECT_FIELD_NAME,
render_failure=None):
redirect_to = request.REQUEST.get(redirect_field_name, '')
data = get_request_data(request)
redirect_to = data.get(redirect_field_name, '')
render_failure = (
render_failure or getattr(settings, 'OPENID_RENDER_FAILURE', None) or
default_render_failure)
......@@ -290,8 +303,9 @@ def login_complete(request, redirect_field_name=REDIRECT_FIELD_NAME,
if openid_response.status == SUCCESS:
try:
user = authenticate(openid_response=openid_response)
except DjangoOpenIDException, e:
return render_failure(request, e.message, exception=e)
except DjangoOpenIDException as e:
return render_failure(
request, getattr(e, 'message', str(e)), exception=e)
if user is not None:
if user.is_active:
......@@ -325,6 +339,7 @@ def logo(request):
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 = """
......
......@@ -54,6 +54,24 @@ SECRET_KEY = '34958734985734985734985798437'
DEBUG = True
TEMPLATE_DEBUG = True
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.contrib.auth.context_processors.auth',
'django.template.context_processors.debug',
'django.template.context_processors.i18n',
'django.template.context_processors.media',
'django.template.context_processors.static',
'django.template.context_processors.tz',
'django.contrib.messages.context_processors.messages',
]
}
}
]
ALLOWED_HOSTS = []
......
......@@ -27,20 +27,20 @@
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
from django.conf.urls import patterns, include, url
from django.conf.urls import include, url
from django.contrib import admin
from django.contrib.auth import views as auth_views
import views
from example_consumer import views
admin.autodiscover()
urlpatterns = patterns(
'',
urlpatterns = [
url(r'^$', views.index),
url(r'^openid/', include('django_openid_auth.urls')),
url(r'^logout/$', 'django.contrib.auth.views.logout'),
url(r'^logout/$', auth_views.logout),
url(r'^private/$', views.require_authentication),
url(r'^admin/', include(admin.site.urls)),
)
]
......@@ -39,21 +39,28 @@ library also includes the following features:
info.
"""
import sys
from setuptools import find_packages, setup
PY3 = sys.version_info.major >= 3
description, long_description = __doc__.split('\n\n', 1)
VERSION = '0.8'
VERSION = '0.14'
install_requires = ['django>=1.6', 'six']
if PY3:
install_requires.append('python3-openid')
else:
install_requires.append('python-openid>=2.2.0')
setup(
name='django-openid-auth',
version=VERSION,
packages=find_packages(),
install_requires=[
'django>=1.5',
'python-openid>=2.2.0',
],
install_requires=install_requires,
package_data={
'django_openid_auth': ['templates/openid/*.html'],
},
......
[tox]
envlist =
py2.7-django1.5, py2.7-django1.6, py2.7-django1.7, py2.7-django1.8
py27-django{1.8,1.9,1.10}
# py3-django{1.11}
[testenv]
commands = python manage.py test django_openid_auth
deps=
deps =
mock
python-openid
[testenv:py2.7-django1.5]
[testenv:py27]
basepython = python2.7
deps =
django >= 1.5, < 1.6
python-openid
{[testenv]deps}
south==1.0
[testenv:py2.7-django1.6]
basepython = python2.7
[testenv:py3]
basepython = python3
deps =
django >= 1.6, < 1.7
python3-openid
{[testenv]deps}
south==1.0
[testenv:py2.7-django1.7]
basepython = python2.7
[testenv:py27-django1.8]
deps =
django >= 1.7, < 1.8
{[testenv]deps}
django >= 1.8, < 1.9
{[testenv:py27]deps}
[testenv:py2.7-django1.8]
basepython = python2.7
[testenv:py27-django1.9]
deps =
django >= 1.8, < 1.9
{[testenv]deps}
django >= 1.9, < 1.10
{[testenv:py27]deps}
[testenv:py27-django1.10]
deps =
django >= 1.10, < 1.11
{[testenv:py27]deps}
[testenv:py27-django1.11]
deps =
django >= 1.11, < 2
{[testenv:py27]deps}
[testenv:py3-django1.11]
deps =
django >= 1.11, < 2
{[testenv:py3]deps}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment