2011-12-04 15 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 

は、今ではそれがとにかくしなければならなかった理由は、私がunderstodd決してGETPOSTから方法を変更するのではなく、OAuthのを使用しています。私はもっ​​とコードを持っているかもしれませんが、もし私がこれを間違っていると言えば、すでに基本的な例に戻るべきです。

は、例えば、私は、ユーザーがログアウトするいくつかの問題を抱えていたと私は、カスタム・ログアウト・ハンドラを記述する必要がありました:

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のみに依存する場合は、1時間前にユーザーを認証する必要があります。

私は2つの組み合わせを使用する傾向があります。アプリの初期使用時には、oauthを使用してaccess_tokenを取得し、APIにアクセスします。その後、私はsigned_requestを利用してaccess_tokenを取得し、ユーザーのコンテンツを自動的にパーソナライズします(ユーザーに自分の声が伝えられ、OAuthフローを再実行せずにAPIにアクセスできるようになります)。

関連する問題