Django =self.base64\u url\u encode(json.dumps({ u'user\u id':self.user\u id, 发布时间:str(int(time.time()), })) sig=self.base64\u url\u enc
=self.base64\u url\u encode(json.dumps({ u'user\u id':self.user\u id, 发布时间:str(int(time.time()), })) sig=self.base64\u url\u encode(hmac.new( self.app_secret,msg=payload,digestmod=hashlib.sha256).digest() 返回信号+'。+有效载荷 @静力学方法 def base64_url_解码(数据): data=data.encode(u'ascii') 数据+='='*(4-(len(数据)%4)) 返回base64.urlsafe\u b64解码(数据) @静力学方法 def base64_url_编码(数据): 返回base64.urlsafe_b64encode(data).rstrip('=')) 类CsrfException(异常): 通过 类BaseHandler(webapp.RequestHandler): facebook=无 用户=无 csrf_protect=True def初始化(自我、请求、响应): “”“每个请求的常规初始化”“” super(BaseHandler,self).初始化(请求,响应) 尝试: self.init_facebook() self.init_csrf() self.response.headers[u'P3P']=u'CP=HONK'#IE中的iframe cookies 除例外情况外,例如: self.log_异常(ex) 提升 def句柄_异常(自我、ex、调试模式): “”“由webapp为未处理的异常调用”“” self.log_异常(ex) self.render(u'error', trace=traceback.format_exc(),debug_mode=debug_mode) def日志_异常(自身、外部): “”“用于减少某些应用程序引擎错误噪音的内部日志记录处理程序”“” msg=((str(ex)或ex.uuuuuuu类uuuuuuuu名称uuuuuuuu)+ u':\n'+回溯。格式\u exc() 如果isinstance(例如,urlfetch.DownloadError)或\ isinstance(例如,死线超出错误)或\ isinstance(例如,CsrfException)或\ isinstance(例如,taskqueue.TransientError): logging.warn(msg) 其他: logging.error(msg) def set_cookie(自身、名称、值、过期=无): “设置cookie” 如果值为“无”: 值='已删除' expires=datetime.timedelta(分钟=-50000) jar=Cookie.SimpleCookie() jar[name]=值 jar[name]['path']=u'/' 如果到期: 如果isinstance(过期,datetime.timedelta): expires=datetime.datetime.now()+过期 如果isinstance(过期,datetime.datetime): expires=expires.strftime(“%a,%d%b%Y%H:%M:%S”) jar[name]['expires']=expires self.response.headers.add_header(*jar.output().split(u':',1)) def呈现(自身、名称、**数据): “”“呈现模板”“” 如果没有数据: 数据={} 数据[u'js_conf']=json.dumps({ u'appId':conf.FACEBOOK\u APP\u ID, u'canvasName':conf.FACEBOOK\u CANVAS\u NAME, u'userIdOnServer':self.user.user\u id如果self.user否则无, }) 数据[u'logged_in_user']=self.user 数据[u'message']=self.get_message() 数据[u'csrf_令牌']=self.csrf_令牌 数据[u'canvas\u name']=conf.FACEBOOK\u canvas\u name self.response.out.write(template.render( os.path.join( dirname(_文件,'templates',name+'.html'), (数据) def init_facebook(self): “”“设置特定于请求的Facebook和用户实例”“” facebook=facebook() 用户=无 #最初的facebook请求是一篇带有签名请求的帖子 如果您在self.request.POST中“签名请求”: 加载签名请求(self.request.get('signed\u request')) #我们重置了获取的方法,因为来自facebook的请求带有 #出于安全原因,签名的请求使用POST,尽管 #事实上,我是一个天才。在webapp中导致request.POST数据丢失。 self.request.method=u'GET' 自我设置( “u”,facebook.user_cookie,datetime.timedelta(分钟=1440)) self.request.cookies中的elif“u”: 加载签名请求(self.request.cookies.get('u')) #尝试加载或创建用户对象 如果facebook.user\u id: user=user.get\u by\u key\u name(facebook.user\u id) 如果用户: #更新存储的访问令牌 如果facebook.access_令牌和\ facebook.access_令牌!=user.access\u令牌: user.access\u token=facebook.access\u token user.put() #如果在实时ping后刷新数据失败 如果user.dirty: user.refresh_data() #如有必要,还原存储的访问令牌 如果不是facebook.access\u令牌: facebook.access\u token=user.access\u token 如果不是用户和facebook.access\u令牌: me=facebook.api(u'/me',{u'fields':_USER_fields}) 尝试: friends=[user[u'id']代表我的用户[u'friends'][u'data']] user=user(key\u name=facebook.user\u id, user\u id=facebook.user\u id,friends=friends, access_token=facebook.access_token,name=me[u'name'], email=me.get(u'email'),picture=me[u'picture']) user.put() 除KeyError外,例如: 通过#如果无法获取最小字段,则忽略 self.facebook=facebook self.user=用户 def初始csrf(自身): “”“根据需要颁发和处理CSRF令牌”“” self.csrf_token=self.request.cookies.get(u'c') 如果不是self.csrf_令牌: self.csrf_token=str(uuid4())[:8] self.set\u cookie('c',self.csrf\u令牌) 如果self.request.method==u'POST'和self.csrf\u保护\ self.csrf_令牌!=self.request.POST.get(u“u csrf”u令牌): 引发CsrfException(u'缺少或无效CSRF令牌') def set_信息(自身,**obj): “简单Django =self.base64\u url\u encode(json.dumps({ u'user\u id':self.user\u id, 发布时间:str(int(time.time()), })) sig=self.base64\u url\u enc,django,facebook,google-app-engine,facebook-graph-api,Django,Facebook,Google App Engine,Facebook Graph Api,=self.base64\u url\u encode(json.dumps({ u'user\u id':self.user\u id, 发布时间:str(int(time.time()), })) sig=self.base64\u url\u encode(hmac.new( self.app_secret,msg=payload,digestmod=hashlib.sha256).digest() 返回信号+'。+有效载荷 @静力学方法 def base64_url_解码(数据): da
# Facebook Application ID and Secret.
FACEBOOK_APP_ID = '103297833078853'
FACEBOOK_APP_SECRET = 'd1f7a3dfeb650b6826456a5660134f58'
# Canvas Page name.
FACEBOOK_CANVAS_NAME = 'cyberfaze'
# A random token for use with the Real-time API.
FACEBOOK_REALTIME_VERIFY_TOKEN = 'RANDOM TOKEN'
# The external URL this application is available at where the Real-time API will
# send it's pings.
EXTERNAL_HREF = 'http://cyberfaze.appspot.com/'
# Facebook User IDs of admins. The poor mans admin system.
ADMIN_USER_IDS = ['5526183']
#!/usr/bin/env python
# coding: utf-8
# Copyright 2011 Facebook, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import os
# dummy config to enable registering django template filters
os.environ[u'DJANGO_SETTINGS_MODULE'] = u'conf'
from google.appengine.dist import use_library
use_library('django', '1.2')
from django.template.defaultfilters import register
from django.utils import simplejson as json
from functools import wraps
from google.appengine.api import urlfetch, taskqueue
from google.appengine.ext import db, webapp
from google.appengine.ext.webapp import util, template
from google.appengine.runtime import DeadlineExceededError
from random import randrange
from uuid import uuid4
import Cookie
import base64
import cgi
import conf
import datetime
import hashlib
import hmac
import logging
import time
import traceback
import urllib
def htmlescape(text):
"""Escape text for use as HTML"""
return cgi.escape(
text, True).replace("'", ''').encode('ascii', 'xmlcharrefreplace')
@register.filter(name=u'get_name')
def get_name(dic, index):
"""Django template filter to render name"""
return dic[index].name
@register.filter(name=u'get_picture')
def get_picture(dic, index):
"""Django template filter to render picture"""
return dic[index].picture
def select_random(lst, limit):
"""Select a limited set of random non Falsy values from a list"""
final = []
size = len(lst)
while limit and size:
index = randrange(min(limit, size))
size = size - 1
elem = lst[index]
lst[index] = lst[size]
if elem:
limit = limit - 1
final.append(elem)
return final
_USER_FIELDS = u'name,email,picture,friends'
class User(db.Model):
user_id = db.StringProperty(required=True)
access_token = db.StringProperty(required=True)
name = db.StringProperty(required=True)
picture = db.StringProperty(required=True)
email = db.StringProperty()
friends = db.StringListProperty()
dirty = db.BooleanProperty()
def refresh_data(self):
"""Refresh this user's data using the Facebook Graph API"""
me = Facebook().api(u'/me',
{u'fields': _USER_FIELDS, u'access_token': self.access_token})
self.dirty = False
self.name = me[u'name']
self.email = me.get(u'email')
self.picture = me[u'picture']
self.friends = [user[u'id'] for user in me[u'friends'][u'data']]
return self.put()
class Run(db.Model):
user_id = db.StringProperty(required=True)
location = db.StringProperty(required=True)
distance = db.FloatProperty(required=True)
date = db.DateProperty(required=True)
@staticmethod
def find_by_user_ids(user_ids, limit=50):
if user_ids:
return Run.gql(u'WHERE user_id IN :1', user_ids).fetch(limit)
else:
return []
@property
def pretty_distance(self):
return u'%.2f' % self.distance
class RunException(Exception):
pass
class FacebookApiError(Exception):
def __init__(self, result):
self.result = result
def __str__(self):
return self.__class__.__name__ + ': ' + json.dumps(self.result)
class Facebook(object):
"""Wraps the Facebook specific logic"""
def __init__(self, app_id=conf.FACEBOOK_APP_ID,
app_secret=conf.FACEBOOK_APP_SECRET):
self.app_id = app_id
self.app_secret = app_secret
self.user_id = None
self.access_token = None
self.signed_request = {}
def api(self, path, params=None, method=u'GET', domain=u'graph'):
"""Make API calls"""
if not params:
params = {}
params[u'method'] = method
if u'access_token' not in params and self.access_token:
params[u'access_token'] = self.access_token
result = json.loads(urlfetch.fetch(
url=u'https://' + domain + u'.facebook.com' + path,
payload=urllib.urlencode(params),
method=urlfetch.POST,
headers={
u'Content-Type': u'application/x-www-form-urlencoded'})
.content)
if isinstance(result, dict) and u'error' in result:
raise FacebookApiError(result)
return result
def load_signed_request(self, signed_request):
"""Load the user state from a signed_request value"""
try:
sig, payload = signed_request.split(u'.', 1)
sig = self.base64_url_decode(sig)
data = json.loads(self.base64_url_decode(payload))
expected_sig = hmac.new(
self.app_secret, msg=payload, digestmod=hashlib.sha256).digest()
# allow the signed_request to function for upto 1 day
if sig == expected_sig and \
data[u'issued_at'] > (time.time() - 86400):
self.signed_request = data
self.user_id = data.get(u'user_id')
self.access_token = data.get(u'oauth_token')
except ValueError, ex:
pass # ignore if can't split on dot
@property
def user_cookie(self):
"""Generate a signed_request value based on current state"""
if not self.user_id:
return
payload = self.base64_url_encode(json.dumps({
u'user_id': self.user_id,
u'issued_at': str(int(time.time())),
}))
sig = self.base64_url_encode(hmac.new(
self.app_secret, msg=payload, digestmod=hashlib.sha256).digest())
return sig + '.' + payload
@staticmethod
def base64_url_decode(data):
data = data.encode(u'ascii')
data += '=' * (4 - (len(data) % 4))
return base64.urlsafe_b64decode(data)
@staticmethod
def base64_url_encode(data):
return base64.urlsafe_b64encode(data).rstrip('=')
class CsrfException(Exception):
pass
class BaseHandler(webapp.RequestHandler):
facebook = None
user = None
csrf_protect = True
def initialize(self, request, response):
"""General initialization for every request"""
super(BaseHandler, self).initialize(request, response)
try:
self.init_facebook()
self.init_csrf()
self.response.headers[u'P3P'] = u'CP=HONK' # iframe cookies in IE
except Exception, ex:
self.log_exception(ex)
raise
def handle_exception(self, ex, debug_mode):
"""Invoked for unhandled exceptions by webapp"""
self.log_exception(ex)
self.render(u'error',
trace=traceback.format_exc(), debug_mode=debug_mode)
def log_exception(self, ex):
"""Internal logging handler to reduce some App Engine noise in errors"""
msg = ((str(ex) or ex.__class__.__name__) +
u': \n' + traceback.format_exc())
if isinstance(ex, urlfetch.DownloadError) or \
isinstance(ex, DeadlineExceededError) or \
isinstance(ex, CsrfException) or \
isinstance(ex, taskqueue.TransientError):
logging.warn(msg)
else:
logging.error(msg)
def set_cookie(self, name, value, expires=None):
"""Set a cookie"""
if value is None:
value = 'deleted'
expires = datetime.timedelta(minutes=-50000)
jar = Cookie.SimpleCookie()
jar[name] = value
jar[name]['path'] = u'/'
if expires:
if isinstance(expires, datetime.timedelta):
expires = datetime.datetime.now() + expires
if isinstance(expires, datetime.datetime):
expires = expires.strftime('%a, %d %b %Y %H:%M:%S')
jar[name]['expires'] = expires
self.response.headers.add_header(*jar.output().split(u': ', 1))
def render(self, name, **data):
"""Render a template"""
if not data:
data = {}
data[u'js_conf'] = json.dumps({
u'appId': conf.FACEBOOK_APP_ID,
u'canvasName': conf.FACEBOOK_CANVAS_NAME,
u'userIdOnServer': self.user.user_id if self.user else None,
})
data[u'logged_in_user'] = self.user
data[u'message'] = self.get_message()
data[u'csrf_token'] = self.csrf_token
data[u'canvas_name'] = conf.FACEBOOK_CANVAS_NAME
self.response.out.write(template.render(
os.path.join(
os.path.dirname(__file__), 'templates', name + '.html'),
data))
def init_facebook(self):
"""Sets up the request specific Facebook and User instance"""
facebook = Facebook()
user = None
# initial facebook request comes in as a POST with a signed_request
if u'signed_request' in self.request.POST:
facebook.load_signed_request(self.request.get('signed_request'))
# we reset the method to GET because a request from facebook with a
# signed_request uses POST for security reasons, despite it
# actually being a GET. in webapp causes loss of request.POST data.
self.request.method = u'GET'
self.set_cookie(
'u', facebook.user_cookie, datetime.timedelta(minutes=1440))
elif 'u' in self.request.cookies:
facebook.load_signed_request(self.request.cookies.get('u'))
# try to load or create a user object
if facebook.user_id:
user = User.get_by_key_name(facebook.user_id)
if user:
# update stored access_token
if facebook.access_token and \
facebook.access_token != user.access_token:
user.access_token = facebook.access_token
user.put()
# refresh data if we failed in doing so after a realtime ping
if user.dirty:
user.refresh_data()
# restore stored access_token if necessary
if not facebook.access_token:
facebook.access_token = user.access_token
if not user and facebook.access_token:
me = facebook.api(u'/me', {u'fields': _USER_FIELDS})
try:
friends = [user[u'id'] for user in me[u'friends'][u'data']]
user = User(key_name=facebook.user_id,
user_id=facebook.user_id, friends=friends,
access_token=facebook.access_token, name=me[u'name'],
email=me.get(u'email'), picture=me[u'picture'])
user.put()
except KeyError, ex:
pass # ignore if can't get the minimum fields
self.facebook = facebook
self.user = user
def init_csrf(self):
"""Issue and handle CSRF token as necessary"""
self.csrf_token = self.request.cookies.get(u'c')
if not self.csrf_token:
self.csrf_token = str(uuid4())[:8]
self.set_cookie('c', self.csrf_token)
if self.request.method == u'POST' and self.csrf_protect and \
self.csrf_token != self.request.POST.get(u'_csrf_token'):
raise CsrfException(u'Missing or invalid CSRF token.')
def set_message(self, **obj):
"""Simple message support"""
self.set_cookie('m', base64.b64encode(json.dumps(obj)) if obj else None)
def get_message(self):
"""Get and clear the current message"""
message = self.request.cookies.get(u'm')
if message:
self.set_message() # clear the current cookie
return json.loads(base64.b64decode(message))
def user_required(fn):
"""Decorator to ensure a user is present"""
@wraps(fn)
def wrapper(*args, **kwargs):
handler = args[0]
if handler.user:
return fn(*args, **kwargs)
handler.redirect(u'/')
return wrapper
class RecentRunsHandler(BaseHandler):
"""Show recent runs for the user and friends"""
def get(self):
if self.user:
friends = {}
for friend in select_random(
User.get_by_key_name(self.user.friends), 30):
friends[friend.user_id] = friend
self.render(u'runs',
friends=friends,
user_recent_runs=Run.find_by_user_ids(
[self.user.user_id], limit=5),
friends_runs=Run.find_by_user_ids(friends.keys()),
)
else:
self.render(u'welcome')
class UserRunsHandler(BaseHandler):
"""Show a specific user's runs, ensure friendship with the logged in user"""
@user_required
def get(self, user_id):
if self.user.friends.count(user_id) or self.user.user_id == user_id:
user = User.get_by_key_name(user_id)
if not user:
self.set_message(type=u'error',
content=u'That user does not use Run with Friends.')
self.redirect(u'/')
return
self.render(u'user',
user=user,
runs=Run.find_by_user_ids([user_id]),
)
else:
self.set_message(type=u'error',
content=u'You are not allowed to see that.')
self.redirect(u'/')
class RunHandler(BaseHandler):
"""Add a run"""
@user_required
def post(self):
try:
location = self.request.POST[u'location'].strip()
if not location:
raise RunException(u'Please specify a location.')
distance = float(self.request.POST[u'distance'].strip())
if distance < 0:
raise RunException(u'Invalid distance.')
date_year = int(self.request.POST[u'date_year'].strip())
date_month = int(self.request.POST[u'date_month'].strip())
date_day = int(self.request.POST[u'date_day'].strip())
if date_year < 0 or date_month < 0 or date_day < 0:
raise RunException(u'Invalid date.')
date = datetime.date(date_year, date_month, date_day)
run = Run(
user_id=self.user.user_id,
location=location,
distance=distance,
date=date,
)
run.put()
title = run.pretty_distance + u' miles @' + location
publish = u'<a onclick=\'publishRun(' + \
json.dumps(htmlescape(title)) + u')\'>Post to facebook.</a>'
self.set_message(type=u'success',
content=u'Added your run. ' + publish)
except RunException, e:
self.set_message(type=u'error', content=unicode(e))
except KeyError:
self.set_message(type=u'error',
content=u'Please specify location, distance & date.')
except ValueError:
self.set_message(type=u'error',
content=u'Please specify a valid distance & date.')
except Exception, e:
self.set_message(type=u'error',
content=u'Unknown error occured. (' + unicode(e) + u')')
self.redirect(u'/')
class RealtimeHandler(BaseHandler):
"""Handles Facebook Real-time API interactions"""
csrf_protect = False
def get(self):
if (self.request.GET.get(u'setup') == u'1' and
self.user and conf.ADMIN_USER_IDS.count(self.user.user_id)):
self.setup_subscription()
self.set_message(type=u'success',
content=u'Successfully setup Real-time subscription.')
elif (self.request.GET.get(u'hub.mode') == u'subscribe' and
self.request.GET.get(u'hub.verify_token') ==
conf.FACEBOOK_REALTIME_VERIFY_TOKEN):
self.response.out.write(self.request.GET.get(u'hub.challenge'))
logging.info(
u'Successful Real-time subscription confirmation ping.')
return
else:
self.set_message(type=u'error',
content=u'You are not allowed to do that.')
self.redirect(u'/')
def post(self):
body = self.request.body
if self.request.headers[u'X-Hub-Signature'] != (u'sha1=' + hmac.new(
self.facebook.app_secret,
msg=body,
digestmod=hashlib.sha1).hexdigest()):
logging.error(
u'Real-time signature check failed: ' + unicode(self.request))
return
data = json.loads(body)
if data[u'object'] == u'user':
for entry in data[u'entry']:
taskqueue.add(url=u'/task/refresh-user/' + entry[u'id'])
logging.info('Added task to queue to refresh user data.')
else:
logging.warn(u'Unhandled Real-time ping: ' + body)
def setup_subscription(self):
path = u'/' + conf.FACEBOOK_APP_ID + u'/subscriptions'
params = {
u'access_token': conf.FACEBOOK_APP_ID + u'|' +
conf.FACEBOOK_APP_SECRET,
u'object': u'user',
u'fields': _USER_FIELDS,
u'callback_url': conf.EXTERNAL_HREF + u'realtime',
u'verify_token': conf.FACEBOOK_REALTIME_VERIFY_TOKEN,
}
response = self.facebook.api(path, params, u'POST')
logging.info(u'Real-time setup API call response: ' + unicode(response))
class RefreshUserHandler(BaseHandler):
"""Used as an App Engine Task to refresh a single user's data if possible"""
csrf_protect = False
def post(self, user_id):
logging.info('Refreshing user data for ' + user_id)
user = User.get_by_key_name(user_id)
if not user:
return
try:
user.refresh_data()
except FacebookApiError:
user.dirty = True
user.put()
def main():
routes = [
(r'/', RecentRunsHandler),
(r'/user/(.*)', UserRunsHandler),
(r'/run', RunHandler),
(r'/realtime', RealtimeHandler),
(r'/task/refresh-user/(.*)', RefreshUserHandler),
]
application = webapp.WSGIApplication(routes,
debug=os.environ.get('SERVER_SOFTWARE', '').startswith('Dev'))
util.run_wsgi_app(application)
if __name__ == u'__main__':
main()
_USER_FIELDS = u'name,email,picture,friends'
_USER_FIELDS = u'name,email,picture'