Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Facebook sign in added. Closes #119

This commit adds support for Facebook sign in, following the same pattern used
in Twitter. The only main difference is that in order to get the user info
the oauth_token has to be stored in the session as we need to run a get('/me')
request. This request need the oauth_token, otherwise it will fail.
  • Loading branch information...
commit f9476a1ebb034b4601f3752aaea1fef418e4fc4b 1 parent eac2ff9
@teleyinex teleyinex authored
View
21 alembic/versions/a0d7c1872e_add_facebook_user_id.py
@@ -0,0 +1,21 @@
+"""add facebook user id
+
+Revision ID: a0d7c1872e
+Revises: 35242069df8c
+Create Date: 2012-06-29 12:18:38.475096
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = 'a0d7c1872e'
+down_revision = '35242069df8c'
+
+from alembic import op
+import sqlalchemy as sa
+
+
+def upgrade():
+ op.add_column('user', sa.Column('facebook_user_id', sa.Integer, unique=True))
+
+def downgrade():
+ op.drop_column('user', 'facebook_user_id')
View
2  pybossa/model.py
@@ -306,6 +306,8 @@ class User(Base, flaskext.login.UserMixin):
flags = Column(Integer)
# Twitter user_id field
twitter_user_id = Column(Integer, unique=True)
+ # Facebook user_id field
+ facebook_user_id = Column(Integer, unique=True)
#: arbitrary additional information about the user in a JSON dict.
info = Column(JSONType, default=dict)
View
18 pybossa/templates/account/signin.html
@@ -1,14 +1,22 @@
{% extends "base.html" %}
{% block content %}
-{% if auth.twitter %}
- {% if next is not none%}
+{% if next is not none%}
+ {% if auth.twitter %}
<a href="{{ url_for('twitter.login', next=next) }}" class="btn btn-info"><i class="icon-twitter"></i> Sign in with Twitter</a>
- {% else %}
- <a href="{{ url_for('twitter.login') }}" class="btn btn-info"><i class="icon-twitter"></i> Sign in with Twitter</a>
{% endif %}
- <hr>
+ {% if auth.facebook %}
+ <a href="{{ url_for('facebook.login', next=next) }}" class="btn btn-primary"><i class="icon-facebook"></i> Sign in with Facebook</a>
+ {% endif %}
+{% else %}
+ {% if auth.twitter %}
+ <a href="{{ url_for('twitter.login') }}" class="btn btn-info"><i class="icon-twitter"></i> Sign in with Twitter</a>
+ {% endif %}
+ {% if auth.facebook %}
+ <a href="{{ url_for('facebook.login') }}" class="btn btn-primary"><i class="icon-facebook"></i> Sign in with Facebook</a>
+ {% endif %}
{% endif %}
+<hr>
{% from "_formhelpers.html" import render_field %}
<form method="post" class="form-horizontal" action="">
View
13 pybossa/util.py
@@ -195,3 +195,16 @@ def __init__(self, c_k, c_s):
consumer_key = c_k, #app.config['TWITTER_CONSUMER_KEY'],
consumer_secret = c_s #app.config['TWITTER_CONSUMER_KEY']
)
+
+class Facebook:
+ oauth = OAuth()
+ def __init__(self, c_k, c_s):
+ self.oauth = self.oauth.remote_app('facebook',
+ base_url='https://graph.facebook.com/',
+ request_token_url=None,
+ access_token_url='/oauth/access_token',
+ authorize_url='https://www.facebook.com/dialog/oauth',
+ consumer_key = c_k, #app.config['FACEBOOK_APP_ID'],
+ consumer_secret = c_s, #app.config['FACEBOOK_APP_SECRET']
+ request_token_params={'scope': 'email'}
+ )
View
11 pybossa/view/account.py
@@ -22,6 +22,7 @@
from pybossa.util import Unique
from pybossa.util import Pagination
from pybossa.util import Twitter
+from pybossa.util import Facebook
blueprint = Blueprint('account', __name__)
@@ -61,15 +62,17 @@ def signin():
if request.method == 'POST' and not form.validate():
flash('Please correct the errors', 'error')
- auth = {'twitter': False}
+ auth = {'twitter': False, 'facebook': False}
if current_user.is_anonymous():
# If Twitter is enabled in config, show the Twitter Sign in button
if ('twitter' in current_app.blueprints):
auth['twitter'] = True
- return render_template('account/signin.html', title="Sign in", form=form, auth=auth, next=request.args.get('next'))
+ if ('facebook' in current_app.blueprints):
+ auth['facebook'] = True
+ #return render_template('account/signin.html', title="Sign in", form=form, auth=auth, next=request.args.get('next'))
# Else use only the default system
- else:
- return render_template('account/signin.html', title="Sign in", form=form, auth=auth, next=request.args.get('next'))
+ #else:
+ return render_template('account/signin.html', title="Sign in", form=form, auth=auth, next=request.args.get('next'))
else:
# User already signed in, so redirect to home page
return redirect(url_for("home"))
View
95 pybossa/view/facebook.py
@@ -0,0 +1,95 @@
+# This file is part of PyBOSSA.
+#
+# PyBOSSA is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# PyBOSSA is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with PyBOSSA. If not, see <http://www.gnu.org/licenses/>.
+
+from flask import Blueprint, request, url_for, flash, redirect, session
+from flaskext.login import login_user, current_user
+
+import pybossa.model as model
+from pybossa.util import Facebook
+# Required to access the config parameters outside a context as we are using
+# Flask 0.8
+# See http://goo.gl/tbhgF for more info
+from pybossa.core import app
+
+# This blueprint will be activated in web.py if the FACEBOOK APP ID and SECRET
+# are available
+blueprint = Blueprint('facebook', __name__)
+facebook = Facebook(app.config['FACEBOOK_APP_ID'],
+ app.config['FACEBOOK_APP_SECRET'])
+
+
+@blueprint.route('/', methods=['GET', 'POST'])
+def login():
+ return facebook.oauth.authorize(callback=url_for('.oauth_authorized',
+ next=request.args.get("next"), _external=True))
+
+
+@facebook.oauth.tokengetter
+def get_facebook_token():
+ if current_user.is_anonymous():
+ return session.get('oauth_token')
+ else:
+ return (current_user.info['facebook_token']['oauth_token'], '')
+
+
+@blueprint.route('/oauth-authorized')
+@facebook.oauth.authorized_handler
+def oauth_authorized(resp):
+ next_url = request.args.get('next') or url_for('home')
+ if resp is None:
+ flash(u'You denied the request to sign in.', 'error')
+ flash(u'Reason: ' + request.args['error_reason'] +\
+ ' ' + request.arts['error_description'], 'error')
+ return redirect(next_url)
+
+ # We have to store the oauth_token in the session to get the USER fields
+ session['oauth_token'] = (resp['access_token'], '')
+ me = facebook.oauth.get('/me')
+
+ user = model.Session.query(model.User)\
+ .filter_by(facebook_user_id=me.data['id']).first()
+
+ # user never signed on
+ first_login = False
+ if user is None:
+ first_login = True
+ facebook_token = dict(
+ oauth_token=resp['access_token']
+ )
+ info = dict(facebook_token=facebook_token)
+ user = model.User(
+ fullname=me.data['name'],
+ name=me.data['username'],
+ email_addr=me.data['email'],
+ facebook_user_id=me.data['id'],
+ info=info
+ )
+ model.Session.add(user)
+ model.Session.commit()
+
+ login_user(user, remember=True)
+ flash("Welcome back %s" % user.fullname, 'success')
+ request_email = False
+ if (user.email_addr == "None"):
+ request_email = True
+
+ if request_email:
+ if first_login:
+ flash("This is your first login, please add a valid e-mail")
+ else:
+ flash("Please update your e-mail address in your profile page")
+ return redirect(url_for('account.update_profile'))
+
+ return redirect(next_url)
View
11 pybossa/web.py
@@ -48,6 +48,17 @@
except:
print "Twitter singin disabled"
+# Enable Facebook if available
+try:
+ if (app.config['FACEBOOK_APP_ID'] and app.config['FACEBOOK_APP_SECRET']):
+ from pybossa.view.facebook import blueprint as facebook
+ app.register_blueprint(facebook, url_prefix='/facebook')
+except Exception as inst:
+ print type(inst)
+ print inst.args
+ print inst
+ print "Facebook singin disabled"
+
def url_for_other_page(page):
args = request.view_args.copy()
View
6 settings_local.py.tmpl
@@ -16,8 +16,10 @@ COPYRIGHT = 'Set Your Institution'
DESCRIPTION = 'Set the description in your config'
## External Auth providers
-#TWITTER_CONSUMER_KEY = ''
-#TWITTER_CONSUMER_SECRET = ''
+# TWITTER_CONSUMER_KEY=''
+# TWITTER_CONSUMER_SECRET=''
+# FACEBOOK_APP_ID=''
+# FACEBOOK_APP_SECRET=''
## list of administrator emails to which error emails get sent
View
8 test/test_web.py
@@ -402,7 +402,7 @@ def test_10_get_application(self):
res = self.app.get('/app/sampleapp', follow_redirects = True)
assert self.html_title("Application: Sample App") in res.data, res
assert "Long desc" in res.data, res
- assert "Short summary" in res.data, res
+ assert "Summary" in res.data, res
assert "Completed tasks" in res.data, res
assert "Edit the application" in res.data, res
assert "Delete the application" in res.data, res
@@ -411,8 +411,9 @@ def test_10_get_application(self):
# Now as an anonymous user
res = self.app.get('/app/sampleapp', follow_redirects = True)
assert self.html_title("Application: Sample App") in res.data, res
+ assert "Overview" in res.data, res
assert "Long desc" in res.data, res
- assert "Short summary" in res.data, res
+ assert "Summary" in res.data, res
assert "Completed tasks" in res.data, res
assert "Edit the application" not in res.data, res
assert "Delete the application" not in res.data, res
@@ -421,8 +422,9 @@ def test_10_get_application(self):
self.register(fullname="Perico Palotes", username="perico")
res = self.app.get('/app/sampleapp', follow_redirects = True)
assert self.html_title("Application: Sample App") in res.data, res
+ assert "Overview" in res.data, res
assert "Long desc" in res.data, res
- assert "Short summary" in res.data, res
+ assert "Summary" in res.data, res
assert "Completed tasks" in res.data, res
assert "Edit the application" not in res.data, res
assert "Delete the application" not in res.data, res
Please sign in to comment.
Something went wrong with that request. Please try again.