Alessandro Molina avatar Alessandro Molina committed 180e79c Draft

First Import

Comments (0)

Files changed (16)

+.pyc
+.swp
+^build
+^dist
+.egg-info
+.idea
+.DS_Store
+recursive-include fbauth/public *
+recursive-include fbauth/templates *.html
+global-exclude *.pyc
+
+About FBAuth
+-------------------------
+
+fbauth is a Pluggable Facebook Authentication application for TurboGears2.
+
+It aims at making easy to implement authentication and registration with
+FaceBook Connect in any TurboGears2 application.
+
+Installing
+-------------------------------
+
+fbauth can be installed both from pypi or from bitbucket::
+
+    easy_install tgapp-fbauth
+
+should just work for most of the users
+
+Plugging fbauth
+----------------------------
+
+In your application *config/app_cfg.py* import **plug**::
+
+    from tgext.pluggable import plug
+
+Then at the *end of the file* call plug with fbauth::
+
+    plug(base_config, 'fbauth')
+
+You will be able to add facebook login, registration and connect
+buttons using the helpers provided by fbauth.
+
+**Keep in mind that facebook connect won't work correctly with
+applications that are not running on port 80**
+
+FaceBook Id and Avatar
+-----------------------
+
+When using FBAuth users will have a new related entity called ``fbauth``.
+Accessing ``user.fbauth`` it is possible to access the user ``user.fbauth.facebook_id``
+and ``user.fbauth.profile_picture``.
+
+FBAuth Helpers
+----------------------
+
+fbauth provides a bunch of helpers which will automatically
+generate the buttons and the javascript required to let
+your users log into your application using FaceBook Connect:
+
+     * **h.fbauth.login_button(appid, text='Login with Facebook', scope=None, remember='')**
+        Places a login button.
+        Login permits to log with an user that has already been connected with a facebook id.
+        To connect an user to a facebook id, *registration* or *connect* can be used.
+
+        The ``appid`` parameter has to be the id of your application, if ``None`` is provided
+        the FB.init call will be skipped so that FB can be manually initialized.
+
+        The ``text`` parameter is the text to show inside the button.
+
+        The ``scope`` parameter is the permissions that the application will ask to facebook.
+        By default those are only user data and email.
+
+        The ``remember`` parameter can be used to log the user with an expiration date instead
+        of using a session cookie, so that the session can last longer than the browser tab life.
+
+     * **h.fbauth.register_button(appid, text='Register with Facebook', scope=None, remember='')**
+        Places a registration button.
+        Registration automatically creates a new user from its facebook data and logs him in.
+        *For registration to work it is required that any additional data apart the data which
+        is already required by default in the quickstart User model can be nullable. A way
+        to identify newly registered users and ask for missing data is provided*
+        If an user for the obtained token already exists that user is logged in instead of
+        creating a new user. This permits to implement 1 click registration and login.
+        Newly created users will have both ``user.fbauth.registered`` and ``user.fbauth.just_connected``
+        flags at ``True`` so that it is possible to identify when users have just registered
+        and ask them more informations that facebook didn't provide. It is suggested to set
+        the ``just_connected`` flag to ``False`` on post_login handler to correctly track
+        users that have just registered for real.
+
+        The ``appid`` parameter has to be the id of your application, if ``None`` is provided
+        the FB.init call will be skipped so that FB can be manually initialized.
+
+        The ``text`` parameter is the text to show inside the button.
+
+        The ``scope`` parameter is the permissions that the application will ask to facebook.
+        By default those are only user data and email.
+
+        The ``remember`` parameter can be used to log the user with an expiration date instead
+        of using a session cookie, so that the session can last longer than the browser tab life.
+
+     * **h.fbauth.connect_button(appid, text='Connect your Facebook account', scope=None)**
+        Places a connect account button.
+        Connect permits to associate an already existing user to a facebook account so that
+        it can later log with its facebook account.
+        Newly connected users will have ``user.fbauth.just_connected`` flag at ``True`` while
+        the ``user.fbauth.registered`` flag will be ``False`` to differentiate users that
+        have been connected from users that have registered with facebook.
+
+        The ``appid`` parameter has to be the id of your application, if ``None`` is provided
+        the FB.init call will be skipped so that FB can be manually initialized.
+
+        The ``text`` parameter is the text to show inside the button.
+
+        The ``scope`` parameter is the permissions that the application will ask to facebook.
+        By default those are only user data and email.
+
+FBAuth Utilities
+------------------
+
+FBAuth provides a bunch of utility methods that make easy to work with facebook:
+
+    * **fbauth.lib.has_fbtoken_expired(user)**
+        Checks if the facebook token for the given users has expired or not, this can be
+        useful when calling facebook API. The facebook token itself can be retrieved from
+        ``user.fbauth.access_token``

fbauth/__init__.py

+# -*- coding: utf-8 -*-
+"""The tgapp-fbauth package"""
+
+def plugme(app_config, options):
+    return dict(appid='fbauth', global_helpers=False)

fbauth/bootstrap.py

+# -*- coding: utf-8 -*-
+"""Setup the fbauth application"""
+
+from fbauth import model
+from tgext.pluggable import app_model
+
+def bootstrap(command, conf, vars):
+    print 'Bootstrapping fbauth...'

fbauth/controllers/__init__.py

+# -*- coding: utf-8 -*-
+"""Controllers for the tgapp-fbauth pluggable application."""
+
+from root import RootController

fbauth/controllers/root.py

+# -*- coding: utf-8 -*-
+"""Main Controller"""
+
+from tg import TGController
+from tg import expose, flash, require, url, lurl, request, redirect, validate, config, require
+from tg.i18n import ugettext as _, lazy_ugettext as l_
+
+try:
+    from tg.predicates import not_anonymous
+except ImportError:
+    from repoze.what.predicates import not_anonymous
+
+from tgext.pluggable import app_model
+
+from fbauth import model
+from fbauth.model import DBSession
+from fbauth.lib.utils import (login_user, has_fbtoken_expired, validate_token,
+                              add_param_to_query_string, redirect_on_fail)
+
+import json
+from urllib import urlopen
+
+class RootController(TGController):
+    @expose()
+    def login(self, token, expiry, came_from=None, remember=None):
+        token, expiry = validate_token(token, expiry)
+
+        fbanswer = urlopen('https://graph.facebook.com/me?access_token=%s' % token)
+        try:
+            answer = json.loads(fbanswer.read())
+            facebook_id = answer['id']
+        except:
+            flash(_('Fatal error while trying to contact Facebook'), 'error')
+            return redirect_on_fail()
+        finally:
+            fbanswer.close()
+
+        user = model.FBAuthInfo.user_by_facebook_id(facebook_id)
+        if not user:
+            flash(_('Unable to find an user for the specified facebook token'), 'error')
+            return redirect_on_fail()
+
+        login_user(user.user_name, remember)
+        if has_fbtoken_expired(user):
+            user.fbauth.access_token = token
+            user.fbauth.access_token_expiry = expiry
+
+        redirect_to = add_param_to_query_string(config.sa_auth['post_login_url'], 'came_from', came_from)
+        return redirect(redirect_to)
+
+    @expose()
+    def register(self, token, expiry, came_from=None, remember=None):
+        token, expiry = validate_token(token, expiry)
+
+        fbanswer = urlopen('https://graph.facebook.com/me?access_token=%s' % token)
+        try:
+            answer = json.loads(fbanswer.read())
+            facebook_id = answer['id']
+        except:
+            flash(_('Fatal error while trying to contact Facebook'), 'error')
+            return redirect_on_fail()
+        finally:
+            fbanswer.close()
+
+        user = model.FBAuthInfo.user_by_facebook_id(facebook_id)
+        if user:
+            #If the user already exists, just login him.
+            login_user(user.user_name, remember)
+            if has_fbtoken_expired(user):
+                user.fbauth.access_token = token
+                user.fbauth.access_token_expiry = expiry
+            redirect_to = add_param_to_query_string(config.sa_auth['post_login_url'], 'came_from', came_from)
+            return redirect(redirect_to)
+
+        u = app_model.User(user_name='fb:%s' % facebook_id,
+                           display_name=answer.get('name',
+                                                   answer.get('username',
+                                                              answer.get('first_name', 'Anonymous'))),
+                           email_address=answer.get('email', '%s@facebook.com' % answer.get('username',
+                                                                                            facebook_id)),
+                           password=token)
+        DBSession.add(u)
+        fbi = model.FBAuthInfo(user=u, facebook_id=facebook_id, registered=True, just_connected=True,
+                               access_token=token, access_token_expiry=expiry,
+                               profile_picture='http://graph.facebook.com/%s/picture' % facebook_id)
+        DBSession.add(fbi)
+
+        login_user(u.user_name, remember)
+        if has_fbtoken_expired(u):
+            u.fbauth.access_token = token
+            u.fbauth.access_token_expiry = expiry
+
+        redirect_to = add_param_to_query_string(config.sa_auth['post_login_url'], 'came_from', came_from)
+        return redirect(redirect_to)
+
+    @expose()
+    @require(not_anonymous())
+    def connect(self, token, expiry, came_from=None):
+        if not came_from:
+            came_from = request.referer or config.sa_auth['post_login_url']
+
+        token, expiry = validate_token(token, expiry)
+
+        fbanswer = urlopen('https://graph.facebook.com/me?access_token=%s' % token)
+        try:
+            answer = json.loads(fbanswer.read())
+            facebook_id = answer['id']
+        except:
+            flash(_('Fatal error while trying to contact Facebook'), 'error')
+            return redirect_on_fail()
+        finally:
+            fbanswer.close()
+
+        user = model.FBAuthInfo.user_by_facebook_id(facebook_id)
+        if user:
+            flash(_('An user for this facebook token is already registered'), 'error')
+            return redirect(came_from)
+
+        u = request.identity['user']
+        fbi = model.FBAuthInfo(user=u, facebook_id=facebook_id, registered=False, just_connected=True,
+                               access_token=token, access_token_expiry=expiry,
+                               profile_picture='http://graph.facebook.com/%s/picture' % facebook_id)
+        DBSession.add(fbi)
+        return redirect(came_from)
+

fbauth/helpers.py

+# -*- coding: utf-8 -*-
+
+"""WebHelpers used in tgapp-fbauth."""
+
+from tg import request
+from markupsafe import Markup
+from urllib import quote_plus
+
+def _fb_init(appid, html, script):
+    if appid:
+        html += '<div id="fb-root"></div>'
+        script += '''<script type="text/javascript">
+        window.fbAsyncInit = function() {
+            FB.init({appId      : "%(appid)s",
+                     status     : false,
+                     cookie     : true,
+                     xfbml      : true,
+                     oauth      : true});
+        };
+        (function() {
+          var e = document.createElement('script'); e.async = true;
+          e.src = document.location.protocol +
+            '//connect.facebook.net/en_US/all.js';
+          document.getElementById('fb-root').appendChild(e);
+        }());
+        </script>''' % dict(appid=appid)
+
+    return html, script
+
+def login_button(appid, text='Login with Facebook', scope=None, remember=''):
+    if not scope:
+        scope = "user_about_me,email"
+
+    html = '''<div class="fb-login-button" data-width="100" scope="%(scope)s" onlogin="fbauth_login()">
+                %(text)s
+              </div>''' % dict(text=text, scope=scope)
+
+    script = '''<script>
+function fbauth_login() {
+    var fbanswer = FB.getAuthResponse();
+    if (fbanswer['accessToken']) {
+        var remember = "%(remember)s";
+        var expiry = fbanswer['expiresIn'];
+        var access_token = fbanswer['accessToken'];
+        var loginUrl = "/fbauth/login/" + access_token + "/" + expiry + "?came_from=%(came_from)s";
+        if (remember)
+            loginUrl += '&remember=' + remember;
+        window.location = loginUrl;
+    }
+}
+</script>''' % dict(remember=remember, came_from=quote_plus(request.url))
+
+    html, script = _fb_init(appid, html, script)
+    return Markup(html + script)
+
+def register_button(appid, text='Register with Facebook', scope=None, remember=''):
+    if not scope:
+        scope = "user_about_me,email"
+
+    html = '''<div class="fb-login-button" scope="%(scope)s" onlogin="fbauth_register()">
+                %(text)s
+              </div>''' % dict(text=text, scope=scope)
+
+    script = '''<script>
+function fbauth_register() {
+    var fbanswer = FB.getAuthResponse();
+    if (fbanswer['accessToken']) {
+        var remember = "%(remember)s";
+        var expiry = fbanswer['expiresIn'];
+        var access_token = fbanswer['accessToken'];
+        var loginUrl = "/fbauth/register/" + access_token + "/" + expiry + "?came_from=%(came_from)s";
+        if (remember)
+            loginUrl += '&remember=' + remember;
+        window.location = loginUrl;
+    }
+}
+</script>''' % dict(remember=remember, came_from=quote_plus(request.url))
+
+    html, script = _fb_init(appid, html, script)
+    return Markup(html + script)
+
+def connect_button(appid, text='Connect your Facebook account', scope=None):
+    if not scope:
+        scope = "user_about_me,email"
+
+    html = '''<div class="fb-login-button" scope="%(scope)s" onlogin="fbauth_connect()">
+                %(text)s
+              </div>''' % dict(text=text, scope=scope)
+
+    script = '''<script>
+function fbauth_connect() {
+    var fbanswer = FB.getAuthResponse();
+    if (fbanswer['accessToken']) {
+        var expiry = fbanswer['expiresIn'];
+        var access_token = fbanswer['accessToken'];
+        var loginUrl = "/fbauth/connect/" + access_token + "/" + expiry + "?came_from=%(came_from)s";
+        window.location = loginUrl;
+    }
+}
+</script>''' % dict(came_from=quote_plus(request.url))
+
+    html, script = _fb_init(appid, html, script)
+    return Markup(html + script)

fbauth/lib/__init__.py

+# -*- coding: utf-8 -*-
+from .utils import has_fbtoken_expired

fbauth/lib/utils.py

+import tg
+from datetime import datetime, timedelta
+from urlparse import urlparse, parse_qs, urlunparse
+from urllib import urlencode
+
+def redirect_on_fail():
+    return tg.redirect(tg.request.referer or tg.config.sa_auth['post_logout_url'])
+
+def validate_token(token, expiry):
+    if not token or not token.strip():
+        tg.flash(_('Missing facebook token'), 'error')
+        return redirect_on_fail()
+
+    try:
+        expiry = expirydate_from_sec(int(expiry))
+    except ValueError:
+        tg.flash(_('Invalid Expiry Time for facebook token'), 'error')
+        return redirect_on_fail()
+
+    return token, expiry
+
+def login_user(user_name, expire=None):
+    request = tg.request
+    response = tg.response
+
+    request.cookies.clear()
+    authentication_plugins = request.environ['repoze.who.plugins']
+    identifier = authentication_plugins['main_identifier']
+
+    login_options = {'repoze.who.userid':user_name}
+    if expire:
+        login_options['max_age'] = expire
+
+    if not request.environ.get('repoze.who.identity'):
+        response.headers = identifier.remember(request.environ, login_options)
+
+def has_fbtoken_expired(user):
+    if not user.fbauth:
+        return True
+
+    expire = user.fbauth.access_token_expiry
+    if not expire:
+        return True
+
+    if datetime.now() > expire:
+        return True
+
+    return False
+
+def expirydate_from_sec(seconds):
+    seconds -= 3
+    if seconds <= 0:
+        raise ValueError('Facebook token already expired')
+
+    return datetime.now() + timedelta(seconds=seconds)
+
+def add_param_to_query_string(url, param, value):
+    url_parts = list(urlparse(url))
+    query_parts = parse_qs(url_parts[4])
+    query_parts[param] = value
+    url_parts[4] = urlencode(query_parts, doseq=True)
+    return urlunparse(url_parts)

fbauth/model/__init__.py

+# -*- coding: utf-8 -*-
+from sqlalchemy.ext.declarative import declarative_base
+from tgext.pluggable import PluggableSession
+
+DBSession = PluggableSession()
+DeclarativeBase = declarative_base()
+
+def init_model(app_session):
+    DBSession.configure(app_session)
+
+from models import FBAuthInfo
+

fbauth/model/models.py

+from sqlalchemy import Table, ForeignKey, Column
+from sqlalchemy.types import Unicode, Integer, DateTime, Boolean, String
+from sqlalchemy.orm import backref, relation
+
+from fbauth.model import DeclarativeBase, DBSession
+from tgext.pluggable import app_model, primary_key
+
+class FBAuthInfo(DeclarativeBase):
+    __tablename__ = 'fbauth_info'
+
+    uid = Column(Integer, autoincrement=True, primary_key=True)
+    registered = Column(Boolean, default=False, nullable=False)
+    just_connected = Column(Boolean, default=False, nullable=False)
+    profile_picture = Column(String(512), nullable=True)
+
+    user_id = Column(Integer, ForeignKey(primary_key(app_model.User)), nullable=False)
+    user = relation(app_model.User, backref=backref('fbauth', uselist=False, cascade='all, delete-orphan'))
+
+    facebook_id = Column(Unicode(255), nullable=False, index=True, unique=True)
+    access_token = Column(Unicode(255), nullable=False)
+    access_token_expiry = Column(DateTime, nullable=False)
+
+    @classmethod
+    def user_by_facebook_id(cls, facebook_id):
+        fbauth = DBSession.query(cls).filter_by(facebook_id=facebook_id).first()
+        if not fbauth:
+            return None
+        return fbauth.user
+

fbauth/partials.py

+from tg import expose

fbauth/public/__init__.py

+# -*- coding: utf-8 -*-
+"""Enable statics for the tgapp-fbauth package"""

fbauth/templates/__init__.py

+# -*- coding: utf-8 -*-
+"""Templates package for the application."""
+# -*- coding: utf-8 -*-
+import sys, os
+
+try:
+    from setuptools import setup, find_packages
+except ImportError:
+    from ez_setup import use_setuptools
+    use_setuptools()
+    from setuptools import setup, find_packages
+
+install_requires=[
+    "TurboGears2 > 2.1.5",
+    "tgext.pluggable"
+]
+
+here = os.path.abspath(os.path.dirname(__file__))
+try:
+    README = open(os.path.join(here, 'README.rst')).read()
+except IOError:
+    README = ''
+
+setup(
+    name='tgapp-fbauth',
+    version='0.1',
+    description='Facebook Authentication for TurboGears2',
+    long_description=README,
+    author='',
+    author_email='',
+    #url='',
+    keywords='turbogears2.application',
+    setup_requires=["PasteScript >= 1.7"],
+    paster_plugins=[],
+    packages=find_packages(exclude=['ez_setup']),
+    install_requires=install_requires,
+    include_package_data=True,
+    package_data={'tgapp.fbauth': ['i18n/*/LC_MESSAGES/*.mo',
+                                 'templates/*/*',
+                                 'public/*/*']},
+    entry_points="""
+    """,
+    dependency_links=[
+        "http://tg.gy/current/"
+        ],
+    zip_safe=False
+)
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.