k8s.io/test-infra@v0.0.0-20240520184403-27c6b4c223d8/gubernator/github_auth.py (about)

     1  # Copyright 2016 The Kubernetes Authors.
     2  #
     3  # Licensed under the Apache License, Version 2.0 (the "License");
     4  # you may not use this file except in compliance with the License.
     5  # You may obtain a copy of the License at
     6  #
     7  #     http://www.apache.org/licenses/LICENSE-2.0
     8  #
     9  # Unless required by applicable law or agreed to in writing, software
    10  # distributed under the License is distributed on an "AS IS" BASIS,
    11  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  # See the License for the specific language governing permissions and
    13  # limitations under the License.
    14  
    15  import json
    16  import logging
    17  import urllib
    18  
    19  from webapp2_extras import security
    20  
    21  from google.appengine.api import urlfetch
    22  
    23  import secrets
    24  import view_base
    25  
    26  
    27  class Endpoint(view_base.BaseHandler):
    28      def github_client(self):
    29          client_key = 'github_client'
    30          if '.appspot.com' not in self.request.host and \
    31              not self.request.host.startswith('localhost:'):
    32              client_key = 'github_client_' + self.request.host
    33          if not self.app.config.get(client_key):
    34              try:
    35                  self.app.config[client_key] = secrets.get(client_key)
    36              except KeyError:
    37                  self.abort(500,
    38                             body_template=(
    39                             'An admin must <a href="/config">'
    40                             'configure GitHub secrets</a> for %r first.'
    41                             % self.request.host))
    42          client = self.app.config[client_key]
    43          return client['id'], client['secret']
    44  
    45      def maybe_redirect(self, target):
    46          """
    47          Redirect to a given URL if it's determined to be safe.
    48          """
    49          if target.startswith('/pr'):
    50              self.redirect(target)
    51  
    52      def get(self, arg):
    53          # Documentation here: https://developer.github.com/v3/oauth/
    54          client_id, client_secret = self.github_client()
    55  
    56          if arg.endswith('/done'):
    57              target, done = arg[:-len('/done')], True
    58          else:
    59              target, done = arg, False
    60  
    61          if not done:
    62              # 1) Redirect users to request GitHub access
    63              if self.session.get('user'):
    64                  # User already logged in, no need to continue.
    65                  self.maybe_redirect(target)
    66                  return
    67  
    68              state = security.generate_random_string(entropy=128)
    69              args = {
    70                  'client_id': client_id,
    71                  'redirect_uri': self.request.url + '/done',
    72                  'scope': '',  # Username only needs "public data" permission!
    73                  'state': state
    74              }
    75              self.session['gh_state'] = state
    76              self.redirect('https://github.com/login/oauth/authorize?'
    77                  + urllib.urlencode(args))
    78          else:
    79              # 2) GitHub redirects back to your site
    80              code = self.request.get('code')
    81              state = self.request.get('state')
    82              session_state = self.session.pop('gh_state', '')
    83  
    84              if not state or not code:
    85                  self.abort(400)
    86              if not security.compare_hashes(state, session_state):
    87                  # States must match to avoid CSRF.
    88                  # Full attack details here:
    89                  # http://homakov.blogspot.com/2012/07/saferweb-most-common-oauth2.html
    90                  self.abort(400)
    91  
    92              # 3) Use the access token to access the API
    93              params = {
    94                  'client_id': client_id,
    95                  'client_secret': client_secret,
    96                  'code': code,
    97                  'state': session_state,
    98              }
    99              resp = urlfetch.fetch(
   100                  'https://github.com/login/oauth/access_token',
   101                  payload=urllib.urlencode(params),
   102                  method='POST',
   103                  headers={'Accept': 'application/json'}
   104              )
   105              if resp.status_code != 200:
   106                  logging.error('failed to vend token: status %d', resp.status_code)
   107                  self.abort(500)
   108              vended = json.loads(resp.content)
   109  
   110              resp = urlfetch.fetch(
   111                  'https://api.github.com/user',
   112                  headers={'Authorization': 'token ' + vended['access_token']})
   113  
   114              if resp.status_code != 200:
   115                  logging.error('failed to get user name: status %d', resp.status_code)
   116                  self.abort(500)
   117  
   118              user_info = json.loads(resp.content)
   119              login = user_info['login']
   120              logging.info('successful login for %s', login)
   121  
   122              # Save the GitHub username to the session.
   123              # Note: we intentionally discard the access_token here,
   124              # since we don't need it for anything more.
   125              self.session['user'] = login
   126              self.response.write('<h1>Welcome, %s</h1>' % login)
   127  
   128              self.maybe_redirect(target)