From 661460d593d32cbc91382b30dbb2a4034701114f Mon Sep 17 00:00:00 2001 From: Evgeny Fadeev Date: Sun, 23 Dec 2012 01:52:07 -0300 Subject: first pass on fixing facebook login, "approve" path works --- askbot/__init__.py | 1 + askbot/deps/django_authopenid/backends.py | 2 +- askbot/deps/django_authopenid/forms.py | 3 +- askbot/deps/django_authopenid/urls.py | 5 ++ askbot/deps/django_authopenid/util.py | 88 ++++++++-------------- askbot/deps/django_authopenid/views.py | 88 +++++++++++++++------- askbot/media/jquery-openid/jquery.openid.js | 7 +- askbot/templates/authopenid/logout.html | 21 ------ .../templates/authopenid/providers_javascript.html | 16 ---- askbot_requirements.txt | 1 + askbot_requirements_dev.txt | 4 + 11 files changed, 108 insertions(+), 128 deletions(-) diff --git a/askbot/__init__.py b/askbot/__init__.py index 72d9c240..d64c4068 100644 --- a/askbot/__init__.py +++ b/askbot/__init__.py @@ -23,6 +23,7 @@ REQUIREMENTS = { 'keyedcache': 'django-keyedcache', 'threaded_multihost': 'django-threaded-multihost', 'robots': 'django-robots', + 'sanction': 'sanction', 'unidecode': 'unidecode', 'django_countries': 'django-countries==1.0.5', 'djcelery': 'django-celery==2.2.7', diff --git a/askbot/deps/django_authopenid/backends.py b/askbot/deps/django_authopenid/backends.py index 1e8626ac..f719e811 100644 --- a/askbot/deps/django_authopenid/backends.py +++ b/askbot/deps/django_authopenid/backends.py @@ -145,7 +145,7 @@ class AuthBackend(object): return None elif method == 'oauth': - if login_providers[provider_name]['type'] == 'oauth': + if login_providers[provider_name]['type'] in ('oauth', 'oauth2'): try: assoc = UserAssociation.objects.get( openid_url = oauth_user_id, diff --git a/askbot/deps/django_authopenid/forms.py b/askbot/deps/django_authopenid/forms.py index 3c0e8e7f..f8822877 100644 --- a/askbot/deps/django_authopenid/forms.py +++ b/askbot/deps/django_authopenid/forms.py @@ -212,7 +212,8 @@ class LoginForm(forms.Form): self.cleaned_data['login_type'] = 'openid' elif provider_type == 'oauth': self.cleaned_data['login_type'] = 'oauth' - pass + elif provider_type == 'oauth2': + self.cleaned_data['login_type'] = 'oauth2' elif provider_type == 'facebook': self.cleaned_data['login_type'] = 'facebook' #self.do_clean_oauth_fields() diff --git a/askbot/deps/django_authopenid/urls.py b/askbot/deps/django_authopenid/urls.py index 1b7d0b01..1d07aa94 100644 --- a/askbot/deps/django_authopenid/urls.py +++ b/askbot/deps/django_authopenid/urls.py @@ -19,6 +19,11 @@ urlpatterns = patterns('askbot.deps.django_authopenid.views', 'complete_oauth_signin', name='user_complete_oauth_signin' ), + url( + r'^signin/complete-oauth2/', + 'complete_oauth2_signin', + name='user_complete_oauth2_signin' + ), url(r'^%s$' % _('register/'), 'register', name='user_register'), url( r'^%s$' % _('signup/'), diff --git a/askbot/deps/django_authopenid/util.py b/askbot/deps/django_authopenid/util.py index e003c493..72ee09df 100644 --- a/askbot/deps/django_authopenid/util.py +++ b/askbot/deps/django_authopenid/util.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- import cgi import urllib -import urllib2 +import urlparse import functools import re import random @@ -9,10 +9,11 @@ from openid.store.interface import OpenIDStore from openid.association import Association as OIDAssociation from openid.extensions import sreg from openid import store as openid_store -import oauth2 as oauth +import oauth2 as oauth # OAuth1 protocol from django.db.models.query import Q from django.conf import settings +from django.core.urlresolvers import reverse from django.utils import simplejson from django.utils.datastructures import SortedDict from django.utils.translation import ugettext as _ @@ -386,12 +387,23 @@ def get_enabled_major_login_providers(): 'password_changeable': True } + def get_facebook_user_id(client): + """returns facebook user id given the access token""" + profile = client.request('me') + return profile['id'] + if askbot_settings.FACEBOOK_KEY and askbot_settings.FACEBOOK_SECRET: data['facebook'] = { 'name': 'facebook', 'display_name': 'Facebook', - 'type': 'facebook', + 'type': 'oauth2', + 'auth_endpoint': 'https://www.facebook.com/dialog/oauth/', + 'token_endpoint': 'https://graph.facebook.com/oauth/access_token', + 'resource_endpoint': 'https://graph.facebook.com/', 'icon_media_path': '/jquery-openid/images/facebook.gif', + 'get_user_id_function': get_facebook_user_id, + 'response_parser': lambda data: dict(urlparse.parse_qsl(data)) + } if askbot_settings.TWITTER_KEY and askbot_settings.TWITTER_SECRET: data['twitter'] = { @@ -666,8 +678,11 @@ def get_oauth_parameters(provider_name): elif provider_name == 'identi.ca': consumer_key = askbot_settings.IDENTICA_KEY consumer_secret = askbot_settings.IDENTICA_SECRET + elif provider_name == 'facebook': + consumer_key = askbot_settings.FACEBOOK_KEY + consumer_secret = askbot_settings.FACEBOOK_SECRET else: - raise ValueError('sorry, only linkedin and twitter oauth for now') + raise ValueError('unexpected oauth provider %s' % provider_name) data['consumer_key'] = consumer_key data['consumer_secret'] = consumer_secret @@ -781,62 +796,21 @@ class OAuthConnection(object): return auth_url -class FacebookError(Exception): - """Raised when there's something not right - with FacebookConnect - """ - pass +def get_oauth2_starter_url(provider_name): + """returns redirect url for the oauth2 protocol for a given provider""" + from sanction.client import Client -def urlsafe_b64decode(input): - length = len(input) - return base64.urlsafe_b64decode( - input.ljust(length + length % 4, '=') + providers = get_enabled_login_providers() + params = providers[provider_name] + client_id = getattr(askbot_settings, provider_name.upper() + '_KEY') + redirect_uri = askbot_settings.APP_URL + reverse('user_complete_oauth2_signin') + client = Client( + auth_endpoint=params['auth_endpoint'], + client_id=client_id, + redirect_uri=redirect_uri ) + return client.auth_uri() -def parse_signed_facebook_request(signed_request, secret): - """ - Parse signed_request given by Facebook (usually via POST), - decrypt with app secret. - - Arguments: - signed_request -- Facebook's signed request given through POST - secret -- Application's app_secret required to decrpyt signed_request - - slightly edited copy from https://gist.github.com/1190267 - """ - - if "." in signed_request: - esig, payload = signed_request.split(".") - else: - return {} - - sig = urlsafe_b64decode(str(esig)) - data = simplejson.loads(urlsafe_b64decode(str(payload))) - - if not isinstance(data, dict): - raise ValueError("Pyload is not a json string!") - return {} - - if data["algorithm"].upper() == "HMAC-SHA256": - if hmac.new(str(secret), str(payload), hashlib.sha256).digest() == sig: - return data - else: - raise ValueError("Not HMAC-SHA256 encrypted!") - - return {} - -def get_facebook_user_id(request): - try: - key = askbot_settings.FACEBOOK_KEY - fb_cookie = request.COOKIES['fbsr_%s' % key] - if not fb_cookie: - raise ValueError('cannot access facebook cookie') - - secret = askbot_settings.FACEBOOK_SECRET - response = parse_signed_facebook_request(fb_cookie, secret) - return response['user_id'] - except Exception, e: - raise FacebookError(e) def ldap_check_password(username, password): import ldap diff --git a/askbot/deps/django_authopenid/views.py b/askbot/deps/django_authopenid/views.py index 1d1a9a57..a7dcabb8 100644 --- a/askbot/deps/django_authopenid/views.py +++ b/askbot/deps/django_authopenid/views.py @@ -54,6 +54,7 @@ from recaptcha_works.decorators import fix_recaptcha_remote_ip from askbot.deps.django_authopenid.ldap_auth import ldap_create_user from askbot.deps.django_authopenid.ldap_auth import ldap_authenticate from askbot.utils.loading import load_module +from sanction.client import Client as OAuth2Client from urlparse import urlparse from openid.consumer.consumer import Consumer, \ @@ -265,6 +266,58 @@ def not_authenticated(func): return func(request, *args, **kwargs) return decorated +def complete_oauth2_signin(request): + if 'next_url' in request.session: + next_url = request.session['next_url'] + del request.session['next_url'] + else: + next_url = reverse('index') + + providers = util.get_enabled_login_providers() + try: + provider_name = request.session['provider_name'] + params = providers[provider_name] + assert(params['type'] == 'oauth2') + except Exception: + return HttpResponseBadRequest() + + client_id = getattr(askbot_settings, provider_name.upper() + '_KEY') + client_secret = getattr(askbot_settings, provider_name.upper() + '_SECRET') + + client = OAuth2Client( + token_endpoint=params['token_endpoint'], + resource_endpoint=params['resource_endpoint'], + redirect_uri=askbot_settings.APP_URL + reverse('user_complete_oauth2_signin'), + client_id=client_id, + client_secret=client_secret + ) + + client.request_token(code=request.GET['code'], parser=params['response_parser']) + + #todo: possibly set additional parameters here + user_id = params['get_user_id_function'](client) + + user = authenticate( + oauth_user_id = user_id, + provider_name = provider_name, + method = 'oauth' + ) + + logging.debug('finalizing oauth signin') + + request.session['email'] = ''#todo: pull from profile + request.session['username'] = ''#todo: pull from profile + + return finalize_generic_signin( + request = request, + user = user, + user_identifier = user_id, + login_provider_name = provider_name, + redirect_url = next_url + ) + + + def complete_oauth_signin(request): if 'next_url' in request.session: next_url = request.session['next_url'] @@ -480,12 +533,10 @@ def signin(request, template_name='authopenid/signin.html'): elif login_form.cleaned_data['login_type'] == 'oauth': try: #this url may need to have "next" piggibacked onto - callback_url = reverse('user_complete_oauth_signin') - connection = util.OAuthConnection( - provider_name, - callback_url = callback_url - ) + provider_name, + callback_url=reverse('user_complete_oauth_signin') + ) connection.start() @@ -504,32 +555,17 @@ def signin(request, template_name='authopenid/signin.html'): ) % {'provider': provider_name} request.user.message_set.create(message = msg) - elif login_form.cleaned_data['login_type'] == 'facebook': - #have to redirect for consistency - #there is a requirement that 'complete_signin' + elif login_form.cleaned_data['login_type'] == 'oauth2': try: - #this call may raise FacebookError - user_id = util.get_facebook_user_id(request) - - user = authenticate( - method = 'facebook', - facebook_user_id = user_id - ) - - return finalize_generic_signin( - request = request, - user = user, - user_identifier = user_id, - login_provider_name = provider_name, - redirect_url = next_url - ) - - except util.FacebookError, e: + redirect_url = util.get_oauth2_starter_url(provider_name) + request.session['provider_name'] = provider_name + return HttpResponseRedirect(redirect_url) + except util.OAuthError, e: logging.critical(unicode(e)) msg = _('Unfortunately, there was some problem when ' 'connecting to %(provider)s, please try again ' 'or use another provider' - ) % {'provider': 'Facebook'} + ) % {'provider': provider_name} request.user.message_set.create(message = msg) elif login_form.cleaned_data['login_type'] == 'wordpress_site': diff --git a/askbot/media/jquery-openid/jquery.openid.js b/askbot/media/jquery-openid/jquery.openid.js index 249413b9..881b2098 100644 --- a/askbot/media/jquery-openid/jquery.openid.js +++ b/askbot/media/jquery-openid/jquery.openid.js @@ -405,12 +405,7 @@ $.fn.authenticator = function() { ); setup_event_handlers( - signin_page.find('input.facebook'), - start_facebook_login - ); - - setup_event_handlers( - signin_page.find('input.oauth'), + signin_page.find('input.oauth,input.oauth2'), start_simple_login ); diff --git a/askbot/templates/authopenid/logout.html b/askbot/templates/authopenid/logout.html index e3e1363f..47579679 100644 --- a/askbot/templates/authopenid/logout.html +++ b/askbot/templates/authopenid/logout.html @@ -5,27 +5,6 @@

{% trans %}You have successfully logged out{% endtrans %}

{% if have_federated_login_methods %}

{% trans %}However, you still may be logged in to your OpenID provider. Please logout of your provider if you wish to do so.{% endtrans %}

- {% if settings.FACEBOOK_KEY and settings.FACEBOOK_SECRET %} -
- - - {% endif %} {% endif %} {% endblock %} -{% block endjs %} - -{% endblock %} diff --git a/askbot/templates/authopenid/providers_javascript.html b/askbot/templates/authopenid/providers_javascript.html index b0ee0ae2..bf9f2542 100644 --- a/askbot/templates/authopenid/providers_javascript.html +++ b/askbot/templates/authopenid/providers_javascript.html @@ -37,19 +37,3 @@ askbot['settings']['signin_always_show_local_login'] = {% if settings.SIGNIN_ALWAYS_SHOW_LOCAL_LOGIN %}true{% else %}false{% endif %}; $("body").authenticator(); -{% if settings.FACEBOOK_KEY and settings.FACEBOOK_SECRET %} -
- - -{% endif %} diff --git a/askbot_requirements.txt b/askbot_requirements.txt index 60f76d52..a9a939b4 100644 --- a/askbot_requirements.txt +++ b/askbot_requirements.txt @@ -19,6 +19,7 @@ django-recaptcha-works python-openid pystache==0.3.1 pytz +sanction django-tinymce longerusername beautifulsoup4 diff --git a/askbot_requirements_dev.txt b/askbot_requirements_dev.txt index 4a90315f..1fbd064c 100644 --- a/askbot_requirements_dev.txt +++ b/askbot_requirements_dev.txt @@ -21,3 +21,7 @@ python-openid pystache==0.3.1 pylint pytz +sanction +django-tinymce +longerusername +beautifulsoup4 -- cgit v1.2.3-1-g7c22