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)