| #!/usr/bin/python | |
| """ | |
| Background daemon to refresh OAuth access tokens. | |
| Tokens are written to ~/.git-credential-cache/cookie | |
| Git config variable http.cookiefile is updated. | |
| Runs only on Google Compute Engine. | |
| """ | |
| import atexit | |
| import contextlib | |
| import cookielib | |
| import json | |
| import os | |
| import subprocess | |
| import sys | |
| import time | |
| import urllib2 | |
| REFRESH = 25 # seconds remaining when starting refresh | |
| RETRY_INTERVAL = 5 # seconds between retrying a failed refresh | |
| A = 'http://metadata/0.1/meta-data/service-accounts/default/acquire' | |
| S = 'https://www.googleapis.com/auth/gerritcodereview' | |
| COOKIE_JAR = None | |
| def configure_git(): | |
| global COOKIE_JAR | |
| dir = os.path.join(os.environ['HOME'], '.git-credential-cache') | |
| COOKIE_JAR = os.path.join(dir, 'cookie') | |
| if os.path.exists(dir): | |
| os.chmod(dir, 0700) | |
| else: | |
| os.mkdir(dir, 0700) | |
| subprocess.call([ | |
| 'git', 'config', '--global', | |
| 'http.cookiefile', COOKIE_JAR | |
| ]) | |
| def acquire_token(retry): | |
| while True: | |
| try: | |
| with contextlib.closing(urllib2.urlopen(A + '?scopes=' + S)) as token: | |
| return json.load(token) | |
| except urllib2.URLError: | |
| if not retry: | |
| raise | |
| time.sleep(RETRY_INTERVAL) | |
| def update_cookie(retry): | |
| token = acquire_token(retry) | |
| expires = token['expiresAt'] | |
| tmp_jar = COOKIE_JAR + '.lock' | |
| cj = cookielib.MozillaCookieJar(tmp_jar) | |
| cj.set_cookie(cookielib.Cookie( | |
| version = 0, | |
| name = 'o', | |
| value = token['accessToken'], | |
| port = None, | |
| port_specified = False, | |
| domain = '.googlesource.com', | |
| domain_specified = True, | |
| domain_initial_dot = True, | |
| path = '/', | |
| path_specified = True, | |
| secure = True, | |
| expires = expires, | |
| discard = False, | |
| comment = None, | |
| comment_url = None, | |
| rest = None)) | |
| cj.save() | |
| os.rename(tmp_jar, COOKIE_JAR) | |
| return expires | |
| def cleanup(): | |
| if COOKIE_JAR: | |
| for p in [COOKIE_JAR, COOKIE_JAR + '.lock']: | |
| if os.path.exists(p): | |
| os.remove(p) | |
| def refresh_loop(expires): | |
| atexit.register(cleanup) | |
| expires = expires - REFRESH | |
| while True: | |
| now = time.time() | |
| expires = max(expires, now + RETRY_INTERVAL) | |
| while now < expires: | |
| time.sleep(expires - now) | |
| now = time.time() | |
| expires = update_cookie(retry=True) - REFRESH | |
| def main(): | |
| configure_git() | |
| expires = update_cookie(retry=False) | |
| if '--nofork' not in sys.argv: | |
| if os.fork() > 0: | |
| sys.exit(0) | |
| os.chdir('/') | |
| os.setsid() | |
| os.umask(0) | |
| pid = os.fork() | |
| if pid > 0: | |
| print '%s PID %d' % (sys.argv[0], pid) | |
| sys.exit(0) | |
| refresh_loop(expires) | |
| if __name__ == '__main__': | |
| main() |