1# Copyright (C) 2019 The Android Open Source Project 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 15import json 16import httplib2 17import os 18import logging 19import threading 20 21from datetime import datetime 22from oauth2client.client import GoogleCredentials 23from config import PROJECT 24 25tls = threading.local() 26 27# Caller has to initialize this 28SCOPES = [] 29 30 31class ConcurrentModificationError(Exception): 32 pass 33 34 35def get_gerrit_credentials(): 36 '''Retrieve the credentials used to authenticate Gerrit requests 37 38 Returns a tuple (user, gitcookie). These fields are obtained from the Gerrit 39 'New HTTP password' page which generates a .gitcookie file and stored in the 40 project datastore. 41 user: typically looks like git-user.gmail.com. 42 gitcookie: is the password after the = token. 43 ''' 44 body = {'query': {'kind': [{'name': 'GerritAuth'}]}} 45 res = req( 46 'POST', 47 'https://datastore.googleapis.com/v1/projects/%s:runQuery' % PROJECT, 48 body=body) 49 auth = res['batch']['entityResults'][0]['entity']['properties'] 50 user = auth['user']['stringValue'] 51 gitcookie = auth['gitcookie']['stringValue'] 52 return user, gitcookie 53 54 55def req(method, uri, body=None, req_etag=False, etag=None, gerrit=False): 56 '''Helper function to handle authenticated HTTP requests. 57 58 Cloud API and Gerrit require two different types of authentication and as 59 such need to be handled differently. The HTTP connection is cached in the 60 TLS slot to avoid refreshing oauth tokens too often for back-to-back requests. 61 Appengine takes care of clearing the TLS slot upon each frontend request so 62 these connections won't be recycled for too long. 63 ''' 64 hdr = {'Content-Type': 'application/json; charset=UTF-8'} 65 tls_key = 'gerrit_http' if gerrit else 'oauth2_http' 66 if hasattr(tls, tls_key): 67 http = getattr(tls, tls_key) 68 else: 69 http = httplib2.Http() 70 setattr(tls, tls_key, http) 71 if gerrit: 72 http.add_credentials(*get_gerrit_credentials()) 73 elif SCOPES: 74 creds = GoogleCredentials.get_application_default().create_scoped(SCOPES) 75 creds.authorize(http) 76 77 if req_etag: 78 hdr['X-Firebase-ETag'] = 'true' 79 if etag: 80 hdr['if-match'] = etag 81 body = None if body is None else json.dumps(body) 82 logging.debug('%s %s', method, uri) 83 resp, res = http.request(uri, method=method, headers=hdr, body=body) 84 if resp.status == 200: 85 res = res[4:] if gerrit else res # Strip Gerrit XSSI projection chars. 86 return (json.loads(res), resp['etag']) if req_etag else json.loads(res) 87 elif resp.status == 412: 88 raise ConcurrentModificationError() 89 else: 90 delattr(tls, tls_key) 91 raise Exception(resp, res) 92 93 94# Datetime functions to deal with the fact that Javascript expects a trailing 95# 'Z' (Z == 'Zulu' == UTC) for timestamps. 96 97 98def parse_iso_time(time_str): 99 return datetime.strptime(time_str, r'%Y-%m-%dT%H:%M:%SZ') 100 101 102def utc_now_iso(utcnow=None): 103 return (utcnow or datetime.utcnow()).strftime(r'%Y-%m-%dT%H:%M:%SZ') 104 105 106def init_logging(): 107 logging.basicConfig( 108 format='%(asctime)s %(levelname)-8s %(message)s', 109 level=logging.DEBUG if os.getenv('VERBOSE') else logging.INFO, 110 datefmt=r'%Y-%m-%d %H:%M:%S') 111