2011-12-04 49 views
4

我看到的OAuth也建議Facebook的畫布應用程序和代碼我有沒有工作,它試圖用signed_request但每次我改變了會議的時間我不得不做了重載:Facebook Canvas應用程序需要簽名的請求嗎?

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)) 

我將上面的代碼更改爲OAuth服務器端的畫布應用程序,然後我可以讓應用程序像我想要的那樣工作。但是,如果我使用OAuth 2.0,我真的需要signed_request嗎?如果我使用OAuth,那麼signed_request似乎不需要,OAuth可以完成這一切。我改變了功能init_facebook相當多:

def init_facebook(self): 

    facebook = Facebook() 
    user = None 

    # initial facebook request comes in as a POST with a signed_request 
    if 'signed_request' in self.request.POST: 
     fbdata= parse_signed_request(self.request.get('signed_request'), facebookconf.FACEBOOK_APP_SECRET) 

     facebook.signed_request = fbdata 
     facebook.user_id = fbdata.get('user_id') 
     facebook.access_token = fbdata.get('oauth_token') 

     if facebook.user_id: 
      graph = GraphAPI(facebook.access_token) 
      user = graph.get_object("me") #write the access_token to the datastore 
      fbuser = FBUser.get_by_key_name(user["id"]) 
      #logging.debug("fbuser "+fbuser.name) 
      self.user = fbuser 
      if not fbuser: 
      fbuser = FBUser(key_name=str(user["id"]), 
          id=str(user["id"]), 
          name=user["name"], 
          profile_url=user["link"], 
          access_token=facebook.access_token) 
      fbuser.put() 
      elif fbuser.access_token != facebook.access_token: 
      fbuser.access_token = facebook.access_token 
      fbuser.put() 

    # try to load or create a user object 
    if facebook.user_id: 
     logging.debug("loading facebook.user_id") 
     user = FBUser.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('/me', {'fields': _USER_FIELDS}) 
      try: 
       friends = [user['id'] for user in me['friends']['data']] 
       user = FBUser(key_name=facebook.user_id, 
        id=facebook.user_id, friends=friends, 
        access_token=facebook.access_token, name=me['name'], 
        email=me.get('email'), picture=me['picture']) 
       user.put() 
      except KeyError, ex: 
       pass # ignore if can't get the minimum fields 

    self.facebook = facebook 
    self.user = user 

現在使用OAuth而不是改變從POST的方法GET這是我從來沒有understodd它爲什麼無論如何做的。我有更多的代碼,但是你可能已經知道了足夠的信息來告訴我我是否做錯了,應該回到一個更基本的例子。

比如我遇到了一些問題,註銷用戶,我不得不編寫自定義註銷處理程序:

class FBLogoutHandler(webapp2.RequestHandler): 
    def get(self): 
    logging.debug('in fblogout') 
     current_user = main.get_user_from_cookie(self.request.cookies, facebookconf.FACEBOOK_APP_ID, facebookconf.FACEBOOK_APP_SECRET) 
     if current_user: 
      graph = main.GraphAPI(current_user["access_token"]) 
      profile = graph.get_object("me") 
     accessed_token = current_user["access_token"] 
    logging.debug('setting cookie') 
     self.set_cookie("fbsr_" + facebookconf.FACEBOOK_APP_ID, None, expires=time.time() - 86400) 
    logging.debug('redirecting with token '+str(accessed_token)) 
     self.redirect('https://www.facebook.com/logout.php?next=http://www.facebook.com&access_token=%s' % accessed_token) 
    def set_cookie(self, name, value, expires=None): 
     if value is None: 
      value = 'deleted' 
      expires = datetime.timedelta(minutes=-50000) 
     jar = Cookie.SimpleCookie() 
     jar[name] = value 
     jar[name]['path'] = '/' 
     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(': ', 1)) 
    def get_host(self): 
     return os.environ.get('HTTP_HOST', os.environ['SERVER_NAME']) 

這種解決方案可能不是最好的,所以我不知道什麼替代品也有對我的畫布應用程序?

感謝

回答

2

signed_request當您訪問畫布應用程序(刷新父框架時),隨時更新,所以它得到的最新access_token用戶的一個很好的方式(通常只持續一小時) 。

使用oauth獲得的access_token具有相同的到期時間,因此,如果完全依賴oauth,則必須每隔一小時對用戶進行身份驗證。

我傾向於使用兩者的組合。在初次使用該應用程序時,我使用oauth獲取access_token並訪問該API。然後,在隨後的訪問中,我依靠signed_request獲取access_token,並自動爲用戶個性化內容(因爲它告訴我他們是誰,並允許我訪問API,而無需再次遵循OAuth流程)。

相關問題