Commit 017e8975 authored by root's avatar root

use pip

parent 086410f9
......@@ -116,7 +116,6 @@ MIDDLEWARE_CLASSES = (
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.cache.FetchFromCacheMiddleware',
'django.contrib.flatpages.middleware.FlatpageFallbackMiddleware',
'edumanage.middleware.WrongBackendExceptionMiddleware',
# Uncomment the next line for simple clickjacking protection:
# 'django.middleware.clickjacking.XFrameOptionsMiddleware',
)
......
from django.core.urlresolvers import reverse
from django.conf import settings
from social_auth.exceptions import WrongBackend
class WrongBackendExceptionMiddleware(object):
def process_exception(self, request, exception):
# Get the exception info now, in case another exception is thrown later.
if isinstance(exception, WrongBackend):
return self.handle_404(request, exception)
from django.core.urlresolvers import RegexURLResolver
def handle_404(self, request, exception):
if settings.DEBUG:
from django.views import debug
return debug.technical_404_response(request, exception)
else:
callback, param_dict = resolver(request).resolve404()
return callback(request, **param_dict)
from django.core.urlresolvers import RegexURLResolver
def resolver(request):
"""
Returns a RegexURLResolver for the request's urlconf.
......
Django==1.4.5
MySQL-python==1.2.3
PyJWT==0.2.1
South==1.0
apt-xapian-index==0.45
argparse==1.2.1
chardet==2.0.1
django-registration==1.0
django-tinymce==1.5.3
gevent==0.13.6
httplib2==0.9
ipaddr==2.1.11
lxml==3.4.0
lxml==2.3.2
oauth2==1.5.211
oauthlib==0.6.3
oauthlib==0.7.1
pexpect==2.4
pycrypto==2.6
python-apt==0.8.8.2
python-openid==2.2.5
requests==2.4.1
python-social-auth==0.2.1
requests==2.4.3
requests-oauthlib==0.4.2
six==1.8.0
wsgiref==0.1.2
python-social-auth==0.2.1
"""
Django-social-auth application, allows OpenId or OAuth user
registration/authentication just adding a few configurations.
"""
version = (0, 7, 18)
__version__ = '.'.join(map(str, version))
"""Admin settings"""
from social_auth.utils import setting
if setting('SOCIAL_AUTH_MODELS') in (None, 'social_auth.db.django_models'):
from django.contrib import admin
from social_auth.models import UserSocialAuth, Nonce, Association
class UserSocialAuthOption(admin.ModelAdmin):
"""Social Auth user options"""
list_display = ('id', 'user', 'provider', 'uid')
search_fields = ('user__first_name', 'user__last_name', 'user__email',
'user__username')
list_filter = ('provider',)
raw_id_fields = ('user',)
list_select_related = True
class NonceOption(admin.ModelAdmin):
"""Nonce options"""
list_display = ('id', 'server_url', 'timestamp', 'salt')
search_fields = ('server_url',)
class AssociationOption(admin.ModelAdmin):
"""Association options"""
list_display = ('id', 'server_url', 'assoc_type')
list_filter = ('assoc_type',)
search_fields = ('server_url',)
admin.site.register(UserSocialAuth, UserSocialAuthOption)
admin.site.register(Nonce, NonceOption)
admin.site.register(Association, AssociationOption)
"""
Base backends structures.
This module defines base classes needed to define custom OpenID or OAuth
auth services from third parties. This customs must subclass an Auth and
and Backend class, check current implementation for examples.
Also the modules *must* define a BACKENDS dictionary with the backend name
(which is used for URLs matching) and Auth class, otherwise it won't be
enabled.
"""
from urllib2 import Request, HTTPError
from urllib import urlencode
from openid.consumer.consumer import Consumer, SUCCESS, CANCEL, FAILURE
from openid.consumer.discover import DiscoveryFailure
from openid.extensions import sreg, ax, pape
from oauth2 import Consumer as OAuthConsumer, Token, Request as OAuthRequest
from django.contrib.auth import authenticate
from django.utils import simplejson
from django.utils.importlib import import_module
from social_auth.models import UserSocialAuth
from social_auth.utils import setting, model_to_ctype, ctype_to_model, \
clean_partial_pipeline, url_add_parameters, \
get_random_string, constant_time_compare, \
dsa_urlopen
from social_auth.store import DjangoOpenIDStore
from social_auth.exceptions import StopPipeline, AuthException, AuthFailed, \
AuthCanceled, AuthUnknownError, \
AuthTokenError, AuthMissingParameter, \
AuthStateMissing, AuthStateForbidden, \
NotAllowedToDisconnect
from social_auth.backends.utils import build_consumer_oauth_request
# OpenID configuration
OLD_AX_ATTRS = [
('http://schema.openid.net/contact/email', 'old_email'),
('http://schema.openid.net/namePerson', 'old_fullname'),
('http://schema.openid.net/namePerson/friendly', 'old_nickname')
]
AX_SCHEMA_ATTRS = [
# Request both the full name and first/last components since some
# providers offer one but not the other.
('http://axschema.org/contact/email', 'email'),
('http://axschema.org/namePerson', 'fullname'),
('http://axschema.org/namePerson/first', 'first_name'),
('http://axschema.org/namePerson/last', 'last_name'),
('http://axschema.org/namePerson/friendly', 'nickname'),
]
SREG_ATTR = [
('email', 'email'),
('fullname', 'fullname'),
('nickname', 'nickname')
]
OPENID_ID_FIELD = 'openid_identifier'
SESSION_NAME = 'openid'
# key for username in user details dict used around, see get_user_details
# method
USERNAME = 'username'
PIPELINE = setting('SOCIAL_AUTH_PIPELINE', (
'social_auth.backends.pipeline.social.social_auth_user',
# Removed by default since it can be a dangerouse behavior that
# could lead to accounts take over.
#'social_auth.backends.pipeline.associate.associate_by_email',
'social_auth.backends.pipeline.user.get_username',
'social_auth.backends.pipeline.user.create_user',
'social_auth.backends.pipeline.social.associate_user',
'social_auth.backends.pipeline.social.load_extra_data',
'social_auth.backends.pipeline.user.update_user_details',
))
class SocialAuthBackend(object):
"""A django.contrib.auth backend that authenticates the user based on
a authentication provider response"""
name = '' # provider name, it's stored in database
supports_inactive_user = False
def authenticate(self, *args, **kwargs):
"""Authenticate user using social credentials
Authentication is made if this is the correct backend, backend
verification is made by kwargs inspection for current backend
name presence.
"""
# Validate backend and arguments. Require that the Social Auth
# response be passed in as a keyword argument, to make sure we
# don't match the username/password calling conventions of
# authenticate.
if not (self.name and kwargs.get(self.name) and 'response' in kwargs):
return None
response = kwargs.get('response')
pipeline = PIPELINE
kwargs = kwargs.copy()
kwargs['backend'] = self
if 'pipeline_index' in kwargs:
pipeline = pipeline[kwargs['pipeline_index']:]
else:
kwargs['details'] = self.get_user_details(response)
kwargs['uid'] = self.get_user_id(kwargs['details'], response)
kwargs['is_new'] = False
out = self.pipeline(pipeline, *args, **kwargs)
if not isinstance(out, dict):
return out
social_user = out.get('social_user')
if social_user:
# define user.social_user attribute to track current social
# account
user = social_user.user
user.social_user = social_user
user.is_new = out.get('is_new')
return user
def pipeline(self, pipeline, *args, **kwargs):
"""Pipeline"""
out = kwargs.copy()
if 'pipeline_index' in kwargs:
base_index = int(kwargs['pipeline_index'])
else:
base_index = 0
for idx, name in enumerate(pipeline):
out['pipeline_index'] = base_index + idx
mod_name, func_name = name.rsplit('.', 1)
mod = import_module(mod_name)
func = getattr(mod, func_name, None)
try:
result = func(*args, **out) or {}
except StopPipeline:
# Clean partial pipeline on stop
if 'request' in kwargs:
clean_partial_pipeline(kwargs['request'])
break
if isinstance(result, dict):
out.update(result)
else:
return result
return out
def extra_data(self, user, uid, response, details):
"""Return default blank user extra data"""
return {}
def get_user_id(self, details, response):
"""Must return a unique ID from values returned on details"""
raise NotImplementedError('Implement in subclass')
def get_user_details(self, response):
"""Must return user details in a know internal struct:
{USERNAME: <username if any>,
'email': <user email if any>,
'fullname': <user full name if any>,
'first_name': <user first name if any>,
'last_name': <user last name if any>}
"""
raise NotImplementedError('Implement in subclass')
@classmethod
def tokens(cls, instance):
"""Return the tokens needed to authenticate the access to any API the
service might provide. The return value will be a dictionary with the
token type name as key and the token value.
instance must be a UserSocialAuth instance.
"""
if instance.extra_data and 'access_token' in instance.extra_data:
return {
'access_token': instance.extra_data['access_token']
}
else:
return {}
def get_user(self, user_id):
"""
Return user with given ID from the User model used by this backend.
This is called by django.contrib.auth.middleware.
"""
return UserSocialAuth.get_user(user_id)
class OAuthBackend(SocialAuthBackend):
"""OAuth authentication backend base class.
EXTRA_DATA defines a set of name that will be stored in
extra_data field. It must be a list of tuples with
name and alias.
Also settings will be inspected to get more values names that should be
stored on extra_data field. Setting name is created from current backend
name (all uppercase) plus _EXTRA_DATA.
access_token is always stored.
"""
EXTRA_DATA = None
ID_KEY = 'id'
def get_user_id(self, details, response):
"""OAuth providers return an unique user id in response"""
return response[self.ID_KEY]
@classmethod
def extra_data(cls, user, uid, response, details=None):
"""Return access_token and extra defined names to store in
extra_data field"""
data = {'access_token': response.get('access_token', '')}
name = cls.name.replace('-', '_').upper()
names = (cls.EXTRA_DATA or []) + setting(name + '_EXTRA_DATA', [])
for entry in names:
if len(entry) == 2:
(name, alias), discard = entry, False
elif len(entry) == 3:
name, alias, discard = entry
elif len(entry) == 1:
name = alias = entry
else: # ???
continue
value = response.get(name)
if discard and not value:
continue
data[alias] = value
return data
class OpenIDBackend(SocialAuthBackend):
"""Generic OpenID authentication backend"""
name = 'openid'
def get_user_id(self, details, response):
"""Return user unique id provided by service"""
return response.identity_url
def values_from_response(self, response, sreg_names=None, ax_names=None):
"""Return values from SimpleRegistration response or
AttributeExchange response if present.
@sreg_names and @ax_names must be a list of name and aliases
for such name. The alias will be used as mapping key.
"""
values = {}
# Use Simple Registration attributes if provided
if sreg_names:
resp = sreg.SRegResponse.fromSuccessResponse(response)
if resp:
values.update((alias, resp.get(name) or '')
for name, alias in sreg_names)
# Use Attribute Exchange attributes if provided
if ax_names:
resp = ax.FetchResponse.fromSuccessResponse(response)
if resp:
for src, alias in ax_names:
name = alias.replace('old_', '')
values[name] = resp.getSingle(src, '') or values.get(name)
return values
def get_user_details(self, response):
"""Return user details from an OpenID request"""
values = {USERNAME: '', 'email': '', 'fullname': '',
'first_name': '', 'last_name': ''}
# update values using SimpleRegistration or AttributeExchange
# values
values.update(self.values_from_response(response,
SREG_ATTR,
OLD_AX_ATTRS +
AX_SCHEMA_ATTRS))
fullname = values.get('fullname') or ''
first_name = values.get('first_name') or ''
last_name = values.get('last_name') or ''
if not fullname and first_name and last_name:
fullname = first_name + ' ' + last_name
elif fullname:
try: # Try to split name for django user storage
first_name, last_name = fullname.rsplit(' ', 1)
except ValueError:
last_name = fullname
values.update({'fullname': fullname, 'first_name': first_name,
'last_name': last_name,
USERNAME: values.get(USERNAME) or
(first_name.title() + last_name.title())})
return values
def extra_data(self, user, uid, response, details):
"""Return defined extra data names to store in extra_data field.
Settings will be inspected to get more values names that should be
stored on extra_data field. Setting name is created from current
backend name (all uppercase) plus _SREG_EXTRA_DATA and
_AX_EXTRA_DATA because values can be returned by SimpleRegistration
or AttributeExchange schemas.
Both list must be a value name and an alias mapping similar to
SREG_ATTR, OLD_AX_ATTRS or AX_SCHEMA_ATTRS
"""
name = self.name.replace('-', '_').upper()
sreg_names = setting(name + '_SREG_EXTRA_DATA')
ax_names = setting(name + '_AX_EXTRA_DATA')
data = self.values_from_response(response, sreg_names, ax_names)
return data
class BaseAuth(object):
"""Base authentication class, new authenticators should subclass
and implement needed methods.
AUTH_BACKEND Authorization backend related with this service
"""
AUTH_BACKEND = None
def __init__(self, request, redirect):
self.request = request
# Use request because some auth providers use POST urls with needed
# GET parameters on it
self.data = request.REQUEST
self.redirect = redirect
def auth_url(self):
"""Must return redirect URL to auth provider"""
raise NotImplementedError('Implement in subclass')
def auth_html(self):
"""Must return login HTML content returned by provider"""
raise NotImplementedError('Implement in subclass')
def auth_complete(self, *args, **kwargs):
"""Completes loging process, must return user instance"""
raise NotImplementedError('Implement in subclass')
def to_session_dict(self, next_idx, *args, **kwargs):
"""Returns dict to store on session for partial pipeline."""
return {
'next': next_idx,
'backend': self.AUTH_BACKEND.name,
'args': tuple(map(model_to_ctype, args)),
'kwargs': dict((key, model_to_ctype(val))
for key, val in kwargs.iteritems())
}
def from_session_dict(self, session_data, *args, **kwargs):
"""Takes session saved data to continue pipeline and merges with any
new extra argument needed. Returns tuple with next pipeline index
entry, arguments and keyword arguments to continue the process."""
args = args[:] + tuple(map(ctype_to_model, session_data['args']))
kwargs = kwargs.copy()
saved_kwargs = dict((key, ctype_to_model(val))
for key, val in session_data['kwargs'].iteritems())
saved_kwargs.update((key, val)
for key, val in kwargs.iteritems())
return (session_data['next'], args, saved_kwargs)
def continue_pipeline(self, *args, **kwargs):
"""Continue previous halted pipeline"""
kwargs.update({
'auth': self,
self.AUTH_BACKEND.name: True
})
return authenticate(*args, **kwargs)
def request_token_extra_arguments(self):
"""Return extra arguments needed on request-token process,
setting is per backend and defined by:
<backend name in uppercase>_REQUEST_TOKEN_EXTRA_ARGUMENTS.
"""
backend_name = self.AUTH_BACKEND.name.upper().replace('-', '_')
return setting(backend_name + '_REQUEST_TOKEN_EXTRA_ARGUMENTS', {})
def auth_extra_arguments(self):
"""Return extra arguments needed on auth process, setting is per
backend and defined by:
<backend name in uppercase>_AUTH_EXTRA_ARGUMENTS.
The defaults can be overriden by GET parameters.
"""
backend_name = self.AUTH_BACKEND.name.upper().replace('-', '_')
extra_arguments = setting(backend_name + '_AUTH_EXTRA_ARGUMENTS', {})
for key, value in extra_arguments.iteritems():
if key in self.data:
extra_arguments[key] = self.data[key]
elif value:
extra_arguments[key] = value
return extra_arguments
@property
def uses_redirect(self):
"""Return True if this provider uses redirect url method,
otherwise return false."""
return True
@classmethod
def enabled(cls):
"""Return backend enabled status, all enabled by default"""
return True
def disconnect(self, user, association_id=None):
"""Deletes current backend from user if associated.
Override if extra operations are needed.
"""
name = self.AUTH_BACKEND.name
if UserSocialAuth.allowed_to_disconnect(user, name, association_id):
if association_id:
UserSocialAuth.get_social_auth_for_user(user)\
.get(id=association_id).delete()
else:
UserSocialAuth.get_social_auth_for_user(user)\
.filter(provider=name)\
.delete()
else:
raise NotAllowedToDisconnect()
def build_absolute_uri(self, path=None):
"""Build absolute URI for given path. Replace http:// schema with
https:// if SOCIAL_AUTH_REDIRECT_IS_HTTPS is defined.
"""
uri = self.request.build_absolute_uri(path)
if setting('SOCIAL_AUTH_REDIRECT_IS_HTTPS'):
uri = uri.replace('http://', 'https://')
return uri
class OpenIdAuth(BaseAuth):
"""OpenId process handling"""
AUTH_BACKEND = OpenIDBackend
def auth_url(self):
"""Return auth URL returned by service"""
openid_request = self.setup_request(self.auth_extra_arguments())
# Construct completion URL, including page we should redirect to
return_to = self.build_absolute_uri(self.redirect)
return openid_request.redirectURL(self.trust_root(), return_to)
def auth_html(self):
"""Return auth HTML returned by service"""
openid_request = self.setup_request(self.auth_extra_arguments())
return_to = self.build_absolute_uri(self.redirect)
form_tag = {'id': 'openid_message'}
return openid_request.htmlMarkup(self.trust_root(), return_to,
form_tag_attrs=form_tag)
def trust_root(self):
"""Return trust-root option"""
return setting('OPENID_TRUST_ROOT') or self.build_absolute_uri('/')
def continue_pipeline(self, *args, **kwargs):
"""Continue previous halted pipeline"""
response = self.consumer().complete(dict(self.data.items()),
self.build_absolute_uri())
kwargs.update({
'auth': self,
'response': response,
self.AUTH_BACKEND.name: True
})
return authenticate(*args, **kwargs)
def auth_complete(self, *args, **kwargs):
"""Complete auth process"""
response = self.consumer().complete(dict(self.data.items()),
self.build_absolute_uri())
if not response:
raise AuthException(self, 'OpenID relying party endpoint')
elif response.status == SUCCESS:
kwargs.update({
'auth': self,
'response': response,
self.AUTH_BACKEND.name: True
})
return authenticate(*args, **kwargs)
elif response.status == FAILURE:
raise AuthFailed(self, response.message)
elif response.status == CANCEL:
raise AuthCanceled(self)
else:
raise AuthUnknownError(self, response.status)
def setup_request(self, extra_params=None):
"""Setup request"""
openid_request = self.openid_request(extra_params)
# Request some user details. Use attribute exchange if provider
# advertises support.
if openid_request.endpoint.supportsType(ax.AXMessage.ns_uri):
fetch_request = ax.FetchRequest()
# Mark all attributes as required, Google ignores optional ones
for attr, alias in (AX_SCHEMA_ATTRS + OLD_AX_ATTRS):
fetch_request.add(ax.AttrInfo(attr, alias=alias,
required=True))