github.com/abayer/test-infra@v0.0.5/prow/cmd/gerrit/third_party/git-cookie-authdaemon (about) 1 #!/usr/bin/python 2 # Copyright (C) 2012 Google Inc. 3 # 4 # Licensed under the Apache License, Version 2.0 (the "License"); 5 # you may not use this file except in compliance with the License. 6 # You may obtain a copy of the License at 7 # 8 # http://www.apache.org/licenses/LICENSE-2.0 9 # 10 # Unless required by applicable law or agreed to in writing, software 11 # distributed under the License is distributed on an "AS IS" BASIS, 12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 # See the License for the specific language governing permissions and 14 # limitations under the License. 15 16 """ 17 Background daemon to refresh OAuth access tokens. 18 Tokens are written to ~/.git-credential-cache/cookie 19 Git config variable http.cookiefile is updated. 20 21 Runs only on Google Compute Engine (GCE). On GCE Windows '--nofork' option 22 is needed. '--debug' option is available. 23 """ 24 25 import atexit 26 import contextlib 27 import cookielib 28 import json 29 import os 30 import platform 31 import subprocess 32 import sys 33 import time 34 import urllib2 35 36 REFRESH = 25 # seconds remaining when starting refresh 37 RETRY_INTERVAL = 5 # seconds between retrying a failed refresh 38 39 META_URL = 'http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/' 40 SUPPORTED_SCOPES = [ 41 'https://www.googleapis.com/auth/gerritcodereview', 42 'https://www.googleapis.com/auth/source.full_control', 43 'https://www.googleapis.com/auth/source.read_write', 44 'https://www.googleapis.com/auth/source.read_only', 45 ] 46 COOKIE_JAR = None 47 IS_WINDOWS = platform.system() == 'Windows' 48 49 def read_meta(part): 50 r = urllib2.Request(META_URL + part) 51 r.add_header('Metadata-Flavor', 'Google') 52 return contextlib.closing(urllib2.urlopen(r)) 53 54 def select_scope(): 55 with read_meta('scopes') as d: 56 avail = set(d.read().split()) 57 scopes = [s for s in SUPPORTED_SCOPES if s in avail] 58 if scopes: 59 return iter(scopes).next() 60 sys.stderr.write('error: VM must have one of these scopes:\n\n') 61 for s in SUPPORTED_SCOPES: 62 sys.stderr.write(' %s\n' % (s)) 63 sys.exit(1) 64 65 def configure_git(): 66 global COOKIE_JAR 67 68 if IS_WINDOWS: 69 # Git for Windows reads %HOMEPATH%/.gitconfig in Command Prompt, 70 # but $HOME/.gitconfig in Cygwin. The two paths can be different. 71 # Cygwin sets env var $HOMEPATH accordingly to %HOMEPATH%. 72 # Set cookie file as %HOMEPATH%/.git-credential-cache/cookie, 73 # so it can be used in both cases. 74 if 'HOMEPATH' in os.environ: 75 homepath = os.environ['HOMEPATH'] 76 else: 77 # When launched as a scheduled task at machine startup 78 # HOMEPATH may not be set. 79 sys.stderr.write('HOMEPATH is not set.\n') 80 sys.exit(1) 81 else: 82 homepath = os.environ['HOME'] 83 dir = os.path.join(homepath, '.git-credential-cache') 84 85 COOKIE_JAR = os.path.join(dir, 'cookie') 86 if '--debug' in sys.argv: 87 print 'Cookie file: %s' % COOKIE_JAR 88 89 if os.path.exists(dir): 90 os.chmod(dir, 0700) 91 else: 92 os.mkdir(dir, 0700) 93 subprocess.call([ 94 'git', 'config', '--global', 95 'http.cookiefile', COOKIE_JAR 96 ]) 97 98 def acquire_token(scope, retry): 99 while True: 100 try: 101 with read_meta('token?scopes=' + scope) as d: 102 return json.load(d) 103 except urllib2.URLError: 104 if not retry: 105 raise 106 time.sleep(RETRY_INTERVAL) 107 108 def update_cookie(scope, retry): 109 now = int(time.time()) 110 token = acquire_token(scope, retry) 111 access_token = token['access_token'] 112 expires = now + int(token['expires_in']) # Epoch in sec 113 114 tmp_jar = COOKIE_JAR + '.lock' 115 cj = cookielib.MozillaCookieJar(tmp_jar) 116 117 for d in ['source.developers.google.com', '.googlesource.com']: 118 cj.set_cookie(cookielib.Cookie( 119 version = 0, 120 name = 'o', 121 value = access_token, 122 port = None, 123 port_specified = False, 124 domain = d, 125 domain_specified = True, 126 domain_initial_dot = d.startswith('.'), 127 path = '/', 128 path_specified = True, 129 secure = True, 130 expires = expires, 131 discard = False, 132 comment = None, 133 comment_url = None, 134 rest = None)) 135 136 cj.save() 137 if '--debug' in sys.argv: 138 print 'Updating %s.' % COOKIE_JAR 139 print 'Expires: %d, %s, in %d seconds'% ( 140 expires, time.ctime(expires), expires - now) 141 sys.stdout.flush() 142 if IS_WINDOWS: 143 # os.rename() below on Windows will raise OSError when dst exists. 144 # See https://docs.python.org/2/library/os.html#os.rename 145 if os.path.isfile(COOKIE_JAR): 146 os.remove(COOKIE_JAR) 147 os.rename(tmp_jar, COOKIE_JAR) 148 return expires 149 150 def cleanup(): 151 if COOKIE_JAR: 152 for p in [COOKIE_JAR, COOKIE_JAR + '.lock']: 153 if os.path.exists(p): 154 os.remove(p) 155 156 def refresh_loop(scope, expires): 157 atexit.register(cleanup) 158 expires = expires - REFRESH 159 while True: 160 now = time.time() 161 expires = max(expires, now + RETRY_INTERVAL) 162 while now < expires: 163 time.sleep(expires - now) 164 now = time.time() 165 expires = update_cookie(scope, retry=True) - REFRESH 166 167 def main(): 168 scope = select_scope() 169 configure_git() 170 171 expires = update_cookie(scope, retry=False) 172 173 if '--nofork' not in sys.argv: 174 if IS_WINDOWS: 175 # os.fork() is not supported on Windows. 176 sys.stderr.write('Add \'--nofork\' on Windows\n') 177 sys.exit(1) 178 179 if os.fork() > 0: 180 sys.exit(0) 181 182 os.chdir('/') 183 os.setsid() 184 os.umask(0) 185 186 pid = os.fork() 187 if pid > 0: 188 print 'git-cookie-authdaemon PID %d' % (pid) 189 sys.exit(0) 190 191 refresh_loop(scope, expires) 192 193 if __name__ == '__main__': 194 main()