Skip to content
This repository has been archived by the owner on Jun 12, 2018. It is now read-only.

added functionality to automatically_authenticate #15

Merged
merged 16 commits into from Apr 16, 2013
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 3 additions & 1 deletion .gitignore
Expand Up @@ -7,4 +7,6 @@ README.html
.fb_access_token .fb_access_token
mark.jpg mark.jpg
*.egg *.egg
*.egg *.egg
*~
*.pyc
19 changes: 19 additions & 0 deletions README.md
Expand Up @@ -50,6 +50,7 @@ To remove this access token, you can call `logout()`:


fbconsole.logout() fbconsole.logout()


See below for other modes of authentication.


### Graph API Basics ### ### Graph API Basics ###


Expand Down Expand Up @@ -122,6 +123,24 @@ altogether:


fbconsole.ACCESS_TOKEN = '<your-access-token>' fbconsole.ACCESS_TOKEN = '<your-access-token>'


As a means to set the `ACCESS_TOKEN`, fbconsole provides an automatic
mechanism (python 2.x only) for authenticating server-side apps by
completing the OAuth process automatically:

# WARNING: only supported for python 2.x
fbconsole.automatically_authenticate(
username, # facebook username for authentication
password, # facebook password for authentication
app_secret, # "app secret" from facebook app settings
redirect_uri, # redirect uri specified in facebook app settings
)

This method for authentication is particularly helpful, for example,
for running cron jobs that grab data from the Graph API on a daily
basis. If you have any trouble using the `automatic_authentication`
method, be sure to double check that the username, password, app
secret, and redirect uri are all consistent with your apps
[facebook settings](https://developers.facebook.com/apps).


### Other Options ### ### Other Options ###


Expand Down
1 change: 1 addition & 0 deletions setup.py
Expand Up @@ -29,6 +29,7 @@
else: else:
install_requires.extend([ install_requires.extend([
'poster', 'poster',
'mechanize',
]) ])


if sys.version_info[0] == 2 and sys.version_info[1] == 5: if sys.version_info[0] == 2 and sys.version_info[1] == 5:
Expand Down
128 changes: 128 additions & 0 deletions src/fbconsole.py
Expand Up @@ -74,6 +74,12 @@
from urllib2 import Request from urllib2 import Request
from urllib import urlencode from urllib import urlencode


# import mechanize in python 2.x
if six.PY3:
mechanize = None
else:
import mechanize

APP_ID = '179745182062082' APP_ID = '179745182062082'
SERVER_PORT = 8080 SERVER_PORT = 8080
ACCESS_TOKEN = None ACCESS_TOKEN = None
Expand All @@ -90,6 +96,7 @@
__all__ = [ __all__ = [
'help', 'help',
'authenticate', 'authenticate',
'automatically_authenticate',
'logout', 'logout',
'graph_url', 'graph_url',
'oauth_url', 'oauth_url',
Expand Down Expand Up @@ -233,6 +240,17 @@ class UnknownApiException(ApiException):
class OAuthException(ApiException): class OAuthException(ApiException):
"""Just an oath exception.""" """Just an oath exception."""


class AutomaticLoginError(Exception):
"""
An error has occurred during login. This can occur for a number
of reasons. Make sure you have correctly specified the username,
password, client_secret, redirect_uri, and APP_ID for your
facebook app:

https://developers.facebook.com/apps
"""
def __str__(self):
return self.__class__.__doc__


def _handle_http_error(e): def _handle_http_error(e):
body = e.read() body = e.read()
Expand Down Expand Up @@ -263,6 +281,43 @@ def _safe_json_load(*args, **kwargs):
else: else:
return json.loads(f.read()) return json.loads(f.read())


def _instantiate_browser(debug=False):
"""Setup the mechanize browser to handle redirects, robots, etc to
programmatically complete oauth.

Helpful resources:
* http://stockrt.github.com/p/emulating-a-browser-in-python-with-mechanize/
"""

# instantiate the mechanize browser
browser = mechanize.Browser()

# set different settings
browser.set_handle_equiv(True)
# browser.set_handle_gzip(True) # this is experimental
browser.set_handle_redirect(True)
browser.set_handle_referer(True)
browser.set_handle_robots(False)
browser.set_handle_refresh(
mechanize._http.HTTPRefreshProcessor(),
max_time=1,
)

# add debug logging
if debug:
browser.set_debug_http(True)
browser.set_debug_redirects(True)
browser.set_debug_responses(True)

# add the cookie jar
cj = cookielib.LWPCookieJar()
browser.set_cookiejar(cj)

# setup the headers
browser.addheaders = [('User-agent', 'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.1) Gecko/2008071615 Fedora/3.0.1-1.fc9 Firefox/3.0.1')]

return browser

def help(): def help():
"""Print out some helpful information""" """Print out some helpful information"""
print ''' print '''
Expand Down Expand Up @@ -308,6 +363,79 @@ def authenticate():
while ACCESS_TOKEN is None: while ACCESS_TOKEN is None:
httpd.handle_request() httpd.handle_request()


def automatically_authenticate(username, password, app_secret, redirect_uri,
debug=False):
"""Authenticate with facebook automatically so that server-side
facebook apps can make api calls that require authorization. A
username, password, and app_secret must be specified
(http://developers.facebook.com/docs/authentication/server-side/)

This method automatically sets the ACCESS_TOKEN so that all
subsequent calls to facebook are authenticated.

If you want to request certain permissions, set the AUTH_SCOPE global
variable to the list of permissions you want.
"""

# use the global APP_ID and AUTH_SCOPE for authentication. this
# method sets the ACCESS_TOKEN at the end
global APP_ID
global AUTH_SCOPE
global ACCESS_TOKEN

# throw an import error as mechanize does not work with python
# 3. upset? contribute! https://github.com/jjlee/mechanize
if mechanize is None:
msg = "automatically_authenticate method is not compatible with "
msg += "python 3.x due to mechanize incapatability."
raise ImportError(msg)

# instantiate the browser
browser = _instantiate_browser(debug=debug)

# the state is a random string that is used in subsequent requests
chars = "abcdefghijklmnopqrstuvwxyz1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ"
state = ''.join((random.choice(chars) for i in range(30)))

# 1. redirect the "user" (a server-side user, in this case) to the
# OAuth dialog
url = "https://www.facebook.com/dialog/oauth?" + urllib.urlencode({
"client_id": APP_ID,
"redirect_uri": redirect_uri,
"scope": ','.join(AUTH_SCOPE),
"state": state,
})
browser.open(url)

# 2. "user" is prompted to authorize your application
browser.select_form(nr=0)
browser.form["email"] = username
browser.form["pass"] = password
response = browser.submit()

# 3. Once the user is redirected back to our app, parse out the
# code generated by facebook
auth_url = urlparse(response.geturl())
oauth = parse_qs(auth_url.query)
if "state" not in oauth:
raise AutomaticLoginError
assert oauth["state"][0] == state, "Inconsistent state: %s != %s" % (
oauth["state"][0], state,
)
code = oauth["code"][0]

# 4. Exchange the code for a user access token for this user's data
url="https://graph.facebook.com/oauth/access_token?"+urllib.urlencode({
"client_id": APP_ID,
"redirect_uri": redirect_uri,
"client_secret": app_secret,
"code": code,
})
browser.open(url)
response = browser.response()
oauth = parse_qs(response.read())
ACCESS_TOKEN = oauth["access_token"][0]

def logout(): def logout():
"""Logout of facebook. This just removes the cached access token.""" """Logout of facebook. This just removes the cached access token."""
if os.path.exists(ACCESS_TOKEN_FILE): if os.path.exists(ACCESS_TOKEN_FILE):
Expand Down