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()