Created
February 19, 2015 10:54
-
-
Save jasonsalas/61e4185d508c52d00c58 to your computer and use it in GitHub Desktop.
OAuth2 consumer for App Engine to access Pushbullet's API
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
application: YOUR_APP_NAME_HERE | |
version: 1 | |
runtime: python27 | |
api_version: 1 | |
threadsafe: true | |
handlers: | |
- url: /.* | |
script: oauth.application | |
libraries: | |
- name: webapp2 | |
version: latest | |
- name: jinja2 | |
version: latest |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="utf-8"/> | |
<style type="text/css"> | |
BODY { font-family:Arial, sans-serif; font-size:14pt; } | |
</style> | |
<title>{{ app_title }}</title> | |
</head> | |
<body> | |
<h1>{{ app_title }}</h1> | |
<hr/> | |
<a href="{{ auth_url }}">{{ auth_link_text }}</a> | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# | |
# OAuth2 consumer for Google App Engine to access Pushbullet | |
# ------------------------------------------------------------------- | |
# This App Engine app handles the acquisition of an access token for | |
# the Pushbullet API (https://docs.pushbullet.com) using the OAuth2 | |
# authorization code flow for web server applications. | |
# | |
# This same flow could be used to generate an access token using | |
# the OAuth2 implicit flow for client apps, by changing the | |
# "response_type" parameter in the authorization URL for your | |
# Pushbullet app at https://www.pushbullet.com/create-client. | |
# | |
# For more, see https://docs.pushbullet.com/#oauth. | |
# | |
# App Engine doesn't include a facility for web sessions, but such | |
# can be rolled manually using cookies and Memcache. | |
# | |
__author__ = '[email protected] (Jason Salas)' | |
import webapp2 | |
import json | |
import httplib2 | |
import urllib | |
import datetime | |
import jinja2 | |
import os | |
from google.appengine.ext import ndb | |
JINJA_ENVIRONMENT = jinja2.Environment(loader=jinja2.FileSystemLoader(os.path.dirname(__file__))) | |
''' Datastore model for user credentials ''' | |
class PushbulletCredentials(ndb.Model): | |
access_token = ndb.StringProperty(indexed=True) | |
name = ndb.StringProperty(indexed=True) | |
email = ndb.StringProperty(indexed=True) | |
date = ndb.DateTimeProperty(auto_now_add=True) | |
class PushbulletAuthorize(webapp2.RequestHandler): | |
def get(self): | |
''' STEP 1: send the browser to Pushbullet's authorization URL ''' | |
# check for valid credentials | |
if self.request.cookies.get("username") is None: | |
form_values = { | |
"app_title" : "OAuth2 login provider", | |
"auth_url" : "", # generate this URL at https://www.pushbullet.com/create-client | |
"auth_link_text" : "signin" | |
} | |
template = JINJA_ENVIRONMENT.get_template("login.html") | |
self.response.out.write(template.render(form_values)) | |
else: | |
self.redirect('/content') | |
class OAuth2RedirectHandler(webapp2.RequestHandler): | |
''' STEP 2: get the authorization code and exchange it for an access token ''' | |
def get(self): | |
error = self.request.get("error") | |
if error is not None: | |
# grab the code from Pushbullet | |
code = self.request.get("code") | |
headers = { 'Content-type': 'application/x-www-form-urlencoded' } | |
body = { | |
"grant_type" : "authorization_code", | |
# generate the following two values at https://www.pushbullet.com/create-client | |
"client_id" : YOUR_CLIENT_ID, | |
"client_secret" : YOUR_CLIENT_SECRET, | |
"code" : code | |
} | |
# exchange the code for an access token | |
http = httplib2.Http() | |
response, content = http.request("https://api.pushbullet.com/oauth2/token", "POST", headers=headers, body=urllib.urlencode(body)) | |
data = json.loads(content) | |
# make an authorized request to Pushbullet to extract the user's real name | |
http.add_credentials(data["access_token"], "") | |
profile_response, profile_content = http.request("https://api.pushbullet.com/v2/users/me", "GET") | |
profile_data = json.loads(profile_content) | |
# save the user's credentials in the data store | |
''' | |
NOTE: Pushbullet's OAuth provider doesn't enforce an expiry for access tokens, | |
and thus doesn't issue refresh tokens, but they *might* expire at some point in the future. | |
''' | |
credentials = PushbulletCredentials() | |
credentials.access_token = data["access_token"] | |
credentials.name = profile_data["name"] | |
credentials.email = profile_data["email"] | |
credentials.put() | |
# save the user's e-mail address in a cookie to persist between handlers | |
cookie_expiry = datetime.datetime.now() + datetime.timedelta(weeks=104) | |
self.response.set_cookie("username", credentials.email, expires=cookie_expiry) | |
# send the browser to the content page to interact with the API | |
self.redirect('/content') | |
class Logout(webapp2.RequestHandler): | |
def get(self): | |
cookie = self.request.cookies.get("username") | |
if cookie is not None: | |
# backdate the cookie's expire date by 40 years to remove it from the browser | |
cookie_expiry = datetime.datetime.now() + datetime.timedelta(weeks=-480) | |
self.response.set_cookie("username", cookie, expires=cookie_expiry) | |
''' TODO: delete the credentials from the database ''' | |
self.redirect('/login') | |
class PageContent(webapp2.RequestHandler): | |
def get(self): | |
# check for valid session credentials | |
cookie = self.request.cookies.get("username") | |
if cookie is not None: | |
''' YOUR AUTHORIZED API CODE GOES HERE ''' | |
else: | |
self.redirect('/login') | |
application = webapp2.WSGIApplication([ | |
('/oauth2_complete', OAuth2RedirectHandler), | |
('/login', PushbulletAuthorize), | |
('/logout', Logout), | |
('/content', PageContent) | |
], debug=False) |
I've written a script which uses https://github.com/randomchars/pushbullet.py to send pushes based on a certain set of conditions. I use some other third-party libraries as well, and it works as expected on my local machine. I then converted it to a GAE app with the intention of scheduling it. I'm new to auth though (and GAE for that matter), so while every other part of my script works fine on GAE, the Pushbullet credentials will not authenticate.
If I comment out the line where I try to authenticate my token, everything else works correctly. It appears that your code here would solve my problem.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This is actually a pretty simple OAuth2 flow, as Pushbullet is really easy to connect to and start using. If you're just starting to learn OAuth, this is a good place to start, in addition to other providers that implement simple setups like Reddit and GitHub The more top-heavy authorization providers like Google and Facebook have more detailed flows with dedicated libraries to help you along the way.