ほんじゃら堂

めんどくさい仕事をラクにする作業自動化レシピ集

Google App Engine で Foursquare APIv2 認証

FoursquareAPIのv2でモバイル版の認証画面が使えたりjsonpが使えたりするらしいので、移行してみた。
OAuth認証処理が少し変わったので、それに合わせて作成した認証用クラスや処理を公開。


下記のような認証用クラスを作成した。


foursquare_oauth.py

from google.appengine.api import urlfetch
from google.appengine.ext import db
from urllib import urlencode
from urllib import quote as urlquote
import logging
from django.utils import simplejson as json
class FsqOAuthToken(db.Model):
"""key_name is uid"""
uid = db.StringProperty(required=True)
token = db.StringProperty(required=True)
created = db.DateTimeProperty(auto_now_add=True)
class FoursquareClient(object):
def __init__(self, consumer_key, consumer_secret, callback_url=None):
""" Constructor."""
self.consumer_key = consumer_key
self.consumer_secret = consumer_secret
self.callback_url = callback_url
def encode(text):
return urlquote(str(text), "")
def get_authorization_url(self, is_mobile=False):
request_url = "https://foursquare.com/oauth2/authenticate"
params = {
"client_id": self.consumer_key,
"response_type": "code",
"redirect_uri": self.callback_url,
}
if is_mobile:
params["display"] = "touch"
payload = urlencode(params)
url = "%s?%s" % (request_url, payload)
return url
def get_access_token(self, code):
request_url = "https://foursquare.com/oauth2/access_token"
params = {
"client_id": self.consumer_key,
"client_secret": self.consumer_secret,
"grant_type": "authorization_code",
"redirect_uri": self.callback_url,
"code": code,
}
payload = urlencode(params)
url = "%s?%s" % (request_url, payload)
rpc = urlfetch.create_rpc(deadline=10.0)
urlfetch.make_fetch_call(rpc, url, method=urlfetch.GET, headers={}, payload=None)
result = rpc.get_result()
data = json.loads(result.content)
if "access_token" in data:
token = data["access_token"]
else:
token = None
return token
def get_userinfo(self, access_token):
response = self.make_request("https://api.foursquare.com/v2/users/self", access_token)
data = json.loads(response.content)
if "response" in data and "user" in data["response"]:
user_info = data["response"]["user"]
token_info = FsqOAuthToken.get_by_key_name(user_info["id"])
if not token_info:
token_info = FsqOAuthToken(
key_name=user_info["id"],
uid=user_info["id"],
token=access_token
)
else:
token_info.access_token = access_token
token_info.put()
return user_info
else:
return None
def get_token_by_uid(self, uid):
return FsqOAuthToken.get_by_key_name(uid)
def make_request(self, url, access_token="", additional_params=None, method=urlfetch.GET):
payload = self.prepare_request(url, access_token, additional_params, method)
if method == urlfetch.GET:
url = "%s?%s" % (url, payload)
payload = None
headers = {}
rpc = urlfetch.create_rpc(deadline=10.0)
urlfetch.make_fetch_call(rpc, url, method=method, headers=headers, payload=payload)
return rpc.get_result()
def prepare_request(self, url, access_token="", additional_params=None, method=urlfetch.GET):
params = {}
if access_token:
params["oauth_token"] = access_token
if additional_params:
params.update(additional_params)
for k,v in params.items():
if isinstance(v, unicode):
params[k] = v.encode('utf8')
return urlencode(params)


下記のような感じで利用してる。
セッションの管理には、appengine_utilitiesというライブラリを使用。


views.py

# -*- coding: utf-8 -*-
import re
import foursquare_oauth
from google.appengine.ext import webapp
import appengine_utilities.sessions
from project1 import settings
def is_iphone(request):
if 'User-Agent' in request.headers:
user_agent = request.headers['User-Agent']
iphone_safari_pattern = re.compile(r"iPhone.+AppleWebKit.+Safari")
m = iphone_safari_pattern.search(user_agent)
return True if m else False
else:
return False
def check_signin(request_handler):
session = appengine_utilities.sessions.Session()
if not session.has_key("userinfo"):
is_user_mobile = is_iphone(request_handler.request)
client = foursquare_oauth.FoursquareClient(settings.FSQ_OAUTH_KEY, settings.FSQ_OAUTH_SECRET, settings.FSQ_OAUTH_CALLBACK_URL)
request_handler.redirect(client.get_authorization_url(is_user_mobile))
return None
else:
return session["userinfo"]
class FoursquareSigninRequestHandler(webapp.RequestHandler):
"""サインインボタンなどをクリックした時に呼ばれるハンドラ"""
def get(self):
userinfo = check_signin(self)
if userinfo:
self.redirect("/")
return
class FoursquareCallbackRequestHandler(webapp.RequestHandler):
"""Foursquareに登録したコールバックURLに対応するハンドラ"""
def get(self):
code = self.request.GET.get("code")
client = foursquare_oauth.FoursquareClient(settings.FSQ_OAUTH_KEY, settings.FSQ_OAUTH_SECRET, settings.FSQ_OAUTH_CALLBACK_URL)
access_token = client.get_access_token(code)
fsq_userinfo = client.get_userinfo(access_token)
session = appengine_utilities.sessions.Session()
if fsq_userinfo:
session["userinfo"] = fsq_userinfo
self.redirect("/")
return