I'm having trouble auth'ing into the Github API as an application in GAE (GAE throws exceptions when I use Github3).

import os, sys
sys.path.append("lib")
import jinja2, webapp2, urllib

from google.appengine.api import users, oauth, urlfetch

JINJA_ENVIRONMENT = jinja2.Environment(
    loader=jinja2.FileSystemLoader(os.path.dirname(__file__)),
    extensions=['jinja2.ext.autoescape'],
    autoescape=True)

class ConsoleLogin(webapp2.RequestHandler):

    def get(self):
        google_user = users.get_current_user()

        if google_user:
            fields = {
                "client_id" : os.environ.get('CLIENT_ID'),
                "scope" : "user, repo"
            }
            url = 'https://github.com/login/oauth/authorize'
            data = urllib.urlencode(fields)
            result = urlfetch.fetch(url=url,
                payload=data,
                method=urlfetch.GET
            )

After this point in the code, you're supposed to get a temporary code from Github.

PROBLEM: I simply can't find it. I saw in the guides that you're supposed to fetch it as a environment variable, but I can't see it.

Extra points for anyone who helps me finish the script in Python. ;)

有帮助吗?

解决方案 3

I'm not saying it's pretty - it's not. This code is ugly as sin, but it works using GAE, Webapp2 and urllib2, rather than other frameworks/libraries.

import os, sys, cgi, json, cookielib
sys.path.append("lib")
import jinja2, webapp2, urllib, urllib2

from google.appengine.api import users, oauth, urlfetch
from webapp2_extras import sessions

JINJA_ENVIRONMENT = jinja2.Environment(
    loader=jinja2.FileSystemLoader(os.path.dirname(__file__)),
    extensions=['jinja2.ext.autoescape'],
    autoescape=True)

class BaseHandler(webapp2.RequestHandler):
    def dispatch(self):
        # Get a session store for this request.
        self.session_store = sessions.get_store(request=self.request)

        try:
            # Dispatch the request.
            webapp2.RequestHandler.dispatch(self)
        finally:
            # Save all sessions.
            self.session_store.save_sessions(self.response)

    @webapp2.cached_property
    def session(self):
        # Returns a session using the default cookie key.
        return self.session_store.get_session()

class ConsoleLogin(BaseHandler):
    def get(self):
        # Set variables to avoid problems later
        code = ''
        url = ''
        access_token = ''
        scope = ''
        username = ''

        google_user = users.get_current_user()

        if google_user:
            url = self.request.url
            if ('code' not in url and not self.session.get('access_token')):
                # First time user coming to site. Redirect to GH for code
                url = 'https://github.com/login/oauth/authorize?scope=user,repo&client_id=' + os.environ.get('CLIENT_ID')
                self.redirect(url)
            elif 'code' in url:
                # User has been to GH, continue with auth process
                code = url.replace('http://localhost:8080/?code=', '')

                # We have code, now get Access Token
                fields = {
                    "client_id" : os.environ.get('CLIENT_ID'),
                    "client_secret" : os.environ.get('CLIENT_SECRET'),
                    "code" : code
                }
                url = 'https://github.com/login/oauth/access_token'
                data = urllib.urlencode(fields)
                result = urlfetch.fetch(url=url,
                    payload=data,
                    method=urlfetch.POST
                )

                # Get the query string
                query_string = str(result.content)

                # Get the access token out of the full query string
                access_token = query_string[13:]
                end_access = access_token.find('&')
                access_token = access_token[:end_access]

                # Get the scope out of the full query string
                start_scope = query_string.find('scope')
                end_scope = query_string.find('token_type')
                start_scope = start_scope + 6   # remove the word 'scope='
                end_scope = end_scope - 1       # remove the & symobol
                scope = query_string[start_scope:end_scope]
                scope = scope.split('%2C')

            # Store the Access Token in a Session Variable
            self.session['access_token'] = access_token
            self.session['scope'] = scope

            # And redirect to the base URL for neatness and to avoid other issues
            self.redirect('/')

            access_token = self.session.get('access_token')
            scope = self.session.get('scope')

            context = {
                'access_token' : access_token,
                'scope' : scope,
                'username' : username,
            }

            # Template Settings
            temp = 'templates/index.html'

            template = JINJA_ENVIRONMENT.get_template(temp)
            self.response.write(template.render(context))


config = {}
config['webapp2_extras.sessions'] = {
    'secret_key': 'the-beatles-will-always-rule',
}

application = webapp2.WSGIApplication([
    ('/', ConsoleLogin),
], debug=True, config=config)

其他提示

Here is the actual implementation for GitHub oAuth authentication. Its is build on Flask instead of Webapp2 but you can easily port the handler to Webapp2. You can have a look to a gae bootstrap project gae-init and the particular snippet was take from a fork that accommodates various oAuth provides gae-init-auth. (note: decorator @github.tokengetter is provided by flask_oauth.py)

github_oauth = oauth.OAuth()

github = github_oauth.remote_app(
    'github',
    base_url='https://api.github.com/',
    request_token_url=None,
    access_token_url='https://github.com/login/oauth/access_token',
    authorize_url='https://github.com/login/oauth/authorize',
    consumer_key=config.CONFIG_DB.github_client_id,
    consumer_secret=config.CONFIG_DB.github_client_secret,
    request_token_params={'scope': 'user:email'},
  )


@app.route('/_s/callback/github/oauth-authorized/')
@github.authorized_handler
def github_authorized(resp):
  if resp is None:
    return 'Access denied: error=%s' % flask.request.args['error']
  flask.session['oauth_token'] = (resp['access_token'], '')
  me = github.get('user')
  user_db = retrieve_user_from_github(me.data)
  return signin_user_db(user_db)


@github.tokengetter
def get_github_oauth_token():
  return flask.session.get('oauth_token')


@app.route('/signin/github/')
def signin_github():
  return github.authorize(
      callback=flask.url_for('github_authorized',
          next=util.get_next_url(),
          _external=True,
        )
    )


def retrieve_user_from_github(response):
  auth_id = 'github_%s' % str(response['id'])
  user_db = model.User.retrieve_one_by('auth_ids', auth_id)
  if user_db:
    return user_db
  return create_user_db(
      auth_id,
      response['name'] or response['login'],
      response['login'],
      response['email'] or '',
    )

You seem to be missing a few items when you run your OAuth authorize request. According to the GitHub API docs, you need to pass 4 parameters to the authorize request (this is the standard for OAuth 2 protocol anyway):

  • the client_id (it comes from your GitHub app registration - are you sure it resides in an OS environment variable? Did you put it there yourself? For testing purposes you could start by putting it as a plain string in code ; you'll do better later once everything works fine) ;
  • the scope (you've already defined it - this is OK) ;
  • a random state that you choose and that will be echoed back to you by GitHub in the next step ;
  • and, more importantly, a redirect_uri to which GitHub will forward the client to once the user has allowed access to his account: it must be a URL on your own website that you will need to handle to fetch both code and state parameters

The redirect_uri could be - for example - http://localhost:8080/oauth/accept_github, and then you need to prepare your app.yaml file and Python code to handle requests made to /oauth/accept_github. In the code that handles these requests, try to show the content of: self.request.get('state') and self.request.get('code'): if everything works fine, they should contain what the GitHub API sends you back. Now you're ready for the next step: convert your code into an access_token :)

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top