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