1*800a58d9SAndroid Build Coastguard Worker#!/usr/bin/env python 2*800a58d9SAndroid Build Coastguard Worker# 3*800a58d9SAndroid Build Coastguard Worker# Copyright 2016 - The Android Open Source Project 4*800a58d9SAndroid Build Coastguard Worker# 5*800a58d9SAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License"); 6*800a58d9SAndroid Build Coastguard Worker# you may not use this file except in compliance with the License. 7*800a58d9SAndroid Build Coastguard Worker# You may obtain a copy of the License at 8*800a58d9SAndroid Build Coastguard Worker# 9*800a58d9SAndroid Build Coastguard Worker# http://www.apache.org/licenses/LICENSE-2.0 10*800a58d9SAndroid Build Coastguard Worker# 11*800a58d9SAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software 12*800a58d9SAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS, 13*800a58d9SAndroid Build Coastguard Worker# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14*800a58d9SAndroid Build Coastguard Worker# See the License for the specific language governing permissions and 15*800a58d9SAndroid Build Coastguard Worker# limitations under the License. 16*800a58d9SAndroid Build Coastguard Worker"""Module for handling Authentication. 17*800a58d9SAndroid Build Coastguard Worker 18*800a58d9SAndroid Build Coastguard WorkerPossible cases of authentication are noted below. 19*800a58d9SAndroid Build Coastguard Worker 20*800a58d9SAndroid Build Coastguard Worker-------------------------------------------------------- 21*800a58d9SAndroid Build Coastguard Worker account | authentcation 22*800a58d9SAndroid Build Coastguard Worker-------------------------------------------------------- 23*800a58d9SAndroid Build Coastguard Worker 24*800a58d9SAndroid Build Coastguard Workergoogle account (e.g. gmail)* | normal oauth2 25*800a58d9SAndroid Build Coastguard Worker 26*800a58d9SAndroid Build Coastguard Worker 27*800a58d9SAndroid Build Coastguard Workerservice account* | oauth2 + private key 28*800a58d9SAndroid Build Coastguard Worker 29*800a58d9SAndroid Build Coastguard Worker-------------------------------------------------------- 30*800a58d9SAndroid Build Coastguard Worker 31*800a58d9SAndroid Build Coastguard Worker* For now, non-google employees (i.e. non @google.com account) or 32*800a58d9SAndroid Build Coastguard Worker non-google-owned service account can not access Android Build API. 33*800a58d9SAndroid Build Coastguard Worker Only local build artifact can be used. 34*800a58d9SAndroid Build Coastguard Worker 35*800a58d9SAndroid Build Coastguard Worker* Google-owned service account, if used, needs to be allowed by 36*800a58d9SAndroid Build Coastguard Worker Android Build team so that acloud can access build api. 37*800a58d9SAndroid Build Coastguard Worker""" 38*800a58d9SAndroid Build Coastguard Worker 39*800a58d9SAndroid Build Coastguard Workerimport logging 40*800a58d9SAndroid Build Coastguard Workerimport os 41*800a58d9SAndroid Build Coastguard Worker 42*800a58d9SAndroid Build Coastguard Workerimport httplib2 43*800a58d9SAndroid Build Coastguard Worker 44*800a58d9SAndroid Build Coastguard Worker# pylint: disable=import-error 45*800a58d9SAndroid Build Coastguard Workerfrom oauth2client import client as oauth2_client 46*800a58d9SAndroid Build Coastguard Workerfrom oauth2client import service_account as oauth2_service_account 47*800a58d9SAndroid Build Coastguard Workerfrom oauth2client.contrib import multistore_file 48*800a58d9SAndroid Build Coastguard Workerfrom oauth2client import tools as oauth2_tools 49*800a58d9SAndroid Build Coastguard Worker 50*800a58d9SAndroid Build Coastguard Workerfrom acloud import errors 51*800a58d9SAndroid Build Coastguard Worker 52*800a58d9SAndroid Build Coastguard Worker 53*800a58d9SAndroid Build Coastguard Workerlogger = logging.getLogger(__name__) 54*800a58d9SAndroid Build Coastguard WorkerHOME_FOLDER = os.path.expanduser("~") 55*800a58d9SAndroid Build Coastguard Worker_WEB_SERVER_DEFAULT_PORT = 8080 56*800a58d9SAndroid Build Coastguard Worker# If there is no specific scope use case, we will always use this default full 57*800a58d9SAndroid Build Coastguard Worker# scopes to run CreateCredentials func and user will only go oauth2 flow once 58*800a58d9SAndroid Build Coastguard Worker# after login with this full scopes credentials. 59*800a58d9SAndroid Build Coastguard Worker_ALL_SCOPES = " ".join(["https://www.googleapis.com/auth/compute", 60*800a58d9SAndroid Build Coastguard Worker "https://www.googleapis.com/auth/logging.write", 61*800a58d9SAndroid Build Coastguard Worker "https://www.googleapis.com/auth/androidbuild.internal", 62*800a58d9SAndroid Build Coastguard Worker "https://www.googleapis.com/auth/devstorage.read_write", 63*800a58d9SAndroid Build Coastguard Worker "https://www.googleapis.com/auth/userinfo.email"]) 64*800a58d9SAndroid Build Coastguard Worker 65*800a58d9SAndroid Build Coastguard Worker 66*800a58d9SAndroid Build Coastguard Workerdef _CreateOauthServiceAccountCreds(email, private_key_path, scopes): 67*800a58d9SAndroid Build Coastguard Worker """Create credentials with a normal service account. 68*800a58d9SAndroid Build Coastguard Worker 69*800a58d9SAndroid Build Coastguard Worker Args: 70*800a58d9SAndroid Build Coastguard Worker email: email address as the account. 71*800a58d9SAndroid Build Coastguard Worker private_key_path: Path to the service account P12 key. 72*800a58d9SAndroid Build Coastguard Worker scopes: string, multiple scopes should be saperated by space. 73*800a58d9SAndroid Build Coastguard Worker Api scopes to request for the oauth token. 74*800a58d9SAndroid Build Coastguard Worker 75*800a58d9SAndroid Build Coastguard Worker Returns: 76*800a58d9SAndroid Build Coastguard Worker An oauth2client.OAuth2Credentials instance. 77*800a58d9SAndroid Build Coastguard Worker 78*800a58d9SAndroid Build Coastguard Worker Raises: 79*800a58d9SAndroid Build Coastguard Worker errors.AuthenticationError: if failed to authenticate. 80*800a58d9SAndroid Build Coastguard Worker """ 81*800a58d9SAndroid Build Coastguard Worker try: 82*800a58d9SAndroid Build Coastguard Worker credentials = oauth2_service_account.ServiceAccountCredentials.from_p12_keyfile( 83*800a58d9SAndroid Build Coastguard Worker email, private_key_path, scopes=scopes) 84*800a58d9SAndroid Build Coastguard Worker except EnvironmentError as e: 85*800a58d9SAndroid Build Coastguard Worker raise errors.AuthenticationError( 86*800a58d9SAndroid Build Coastguard Worker f"Could not authenticate using private key file ({private_key_path}) " 87*800a58d9SAndroid Build Coastguard Worker f" error message: {str(e)}") 88*800a58d9SAndroid Build Coastguard Worker return credentials 89*800a58d9SAndroid Build Coastguard Worker 90*800a58d9SAndroid Build Coastguard Worker 91*800a58d9SAndroid Build Coastguard Worker# pylint: disable=invalid-name 92*800a58d9SAndroid Build Coastguard Workerdef _CreateOauthServiceAccountCredsWithJsonKey(json_private_key_path, scopes, 93*800a58d9SAndroid Build Coastguard Worker creds_cache_file, user_agent): 94*800a58d9SAndroid Build Coastguard Worker """Create credentials with a normal service account from json key file. 95*800a58d9SAndroid Build Coastguard Worker 96*800a58d9SAndroid Build Coastguard Worker Args: 97*800a58d9SAndroid Build Coastguard Worker json_private_key_path: Path to the service account json key file. 98*800a58d9SAndroid Build Coastguard Worker scopes: string, multiple scopes should be saperated by space. 99*800a58d9SAndroid Build Coastguard Worker Api scopes to request for the oauth token. 100*800a58d9SAndroid Build Coastguard Worker creds_cache_file: String, file name for the credential cache. 101*800a58d9SAndroid Build Coastguard Worker e.g. .acloud_oauth2.dat 102*800a58d9SAndroid Build Coastguard Worker Will be created at home folder. 103*800a58d9SAndroid Build Coastguard Worker user_agent: String, the user agent for the credential, e.g. "acloud" 104*800a58d9SAndroid Build Coastguard Worker 105*800a58d9SAndroid Build Coastguard Worker Returns: 106*800a58d9SAndroid Build Coastguard Worker An oauth2client.OAuth2Credentials instance. 107*800a58d9SAndroid Build Coastguard Worker 108*800a58d9SAndroid Build Coastguard Worker Raises: 109*800a58d9SAndroid Build Coastguard Worker errors.AuthenticationError: if failed to authenticate. 110*800a58d9SAndroid Build Coastguard Worker """ 111*800a58d9SAndroid Build Coastguard Worker try: 112*800a58d9SAndroid Build Coastguard Worker credentials = oauth2_service_account.ServiceAccountCredentials.from_json_keyfile_name( 113*800a58d9SAndroid Build Coastguard Worker json_private_key_path, scopes=scopes) 114*800a58d9SAndroid Build Coastguard Worker storage = multistore_file.get_credential_storage( 115*800a58d9SAndroid Build Coastguard Worker filename=os.path.abspath(creds_cache_file), 116*800a58d9SAndroid Build Coastguard Worker client_id=credentials.client_id, 117*800a58d9SAndroid Build Coastguard Worker user_agent=user_agent, 118*800a58d9SAndroid Build Coastguard Worker scope=scopes) 119*800a58d9SAndroid Build Coastguard Worker credentials.set_store(storage) 120*800a58d9SAndroid Build Coastguard Worker except EnvironmentError as e: 121*800a58d9SAndroid Build Coastguard Worker raise errors.AuthenticationError( 122*800a58d9SAndroid Build Coastguard Worker f"Could not authenticate using json private key file ({json_private_key_path}) " 123*800a58d9SAndroid Build Coastguard Worker f"error message: {str(e)}") 124*800a58d9SAndroid Build Coastguard Worker 125*800a58d9SAndroid Build Coastguard Worker return credentials 126*800a58d9SAndroid Build Coastguard Worker 127*800a58d9SAndroid Build Coastguard Worker 128*800a58d9SAndroid Build Coastguard Workerclass RunFlowFlags(): 129*800a58d9SAndroid Build Coastguard Worker """Flags for oauth2client.tools.run_flow.""" 130*800a58d9SAndroid Build Coastguard Worker 131*800a58d9SAndroid Build Coastguard Worker def __init__(self, browser_auth): 132*800a58d9SAndroid Build Coastguard Worker self.auth_host_port = [8080, 8090] 133*800a58d9SAndroid Build Coastguard Worker self.auth_host_name = "localhost" 134*800a58d9SAndroid Build Coastguard Worker self.logging_level = "ERROR" 135*800a58d9SAndroid Build Coastguard Worker self.noauth_local_webserver = not browser_auth 136*800a58d9SAndroid Build Coastguard Worker 137*800a58d9SAndroid Build Coastguard Worker 138*800a58d9SAndroid Build Coastguard Workerdef _RunAuthFlow(storage, client_id, client_secret, user_agent, scopes): 139*800a58d9SAndroid Build Coastguard Worker """Get user oauth2 credentials. 140*800a58d9SAndroid Build Coastguard Worker 141*800a58d9SAndroid Build Coastguard Worker Using the loopback IP address flow for desktop clients. 142*800a58d9SAndroid Build Coastguard Worker 143*800a58d9SAndroid Build Coastguard Worker Args: 144*800a58d9SAndroid Build Coastguard Worker client_id: String, client id from the cloud project. 145*800a58d9SAndroid Build Coastguard Worker client_secret: String, client secret for the client_id. 146*800a58d9SAndroid Build Coastguard Worker user_agent: The user agent for the credential, e.g. "acloud" 147*800a58d9SAndroid Build Coastguard Worker scopes: String, scopes separated by space. 148*800a58d9SAndroid Build Coastguard Worker 149*800a58d9SAndroid Build Coastguard Worker Returns: 150*800a58d9SAndroid Build Coastguard Worker An oauth2client.OAuth2Credentials instance. 151*800a58d9SAndroid Build Coastguard Worker """ 152*800a58d9SAndroid Build Coastguard Worker flags = RunFlowFlags(browser_auth=True) 153*800a58d9SAndroid Build Coastguard Worker flow = oauth2_client.OAuth2WebServerFlow( 154*800a58d9SAndroid Build Coastguard Worker client_id=client_id, 155*800a58d9SAndroid Build Coastguard Worker client_secret=client_secret, 156*800a58d9SAndroid Build Coastguard Worker scope=scopes, 157*800a58d9SAndroid Build Coastguard Worker user_agent=user_agent, 158*800a58d9SAndroid Build Coastguard Worker redirect_uri=f"http://localhost:{_WEB_SERVER_DEFAULT_PORT}") 159*800a58d9SAndroid Build Coastguard Worker credentials = oauth2_tools.run_flow( 160*800a58d9SAndroid Build Coastguard Worker flow=flow, storage=storage, flags=flags) 161*800a58d9SAndroid Build Coastguard Worker return credentials 162*800a58d9SAndroid Build Coastguard Worker 163*800a58d9SAndroid Build Coastguard Worker 164*800a58d9SAndroid Build Coastguard Workerdef _CreateOauthUserCreds(creds_cache_file, client_id, client_secret, 165*800a58d9SAndroid Build Coastguard Worker user_agent, scopes): 166*800a58d9SAndroid Build Coastguard Worker """Get user oauth2 credentials. 167*800a58d9SAndroid Build Coastguard Worker 168*800a58d9SAndroid Build Coastguard Worker Args: 169*800a58d9SAndroid Build Coastguard Worker creds_cache_file: String, file name for the credential cache. 170*800a58d9SAndroid Build Coastguard Worker e.g. .acloud_oauth2.dat 171*800a58d9SAndroid Build Coastguard Worker Will be created at home folder. 172*800a58d9SAndroid Build Coastguard Worker client_id: String, client id from the cloud project. 173*800a58d9SAndroid Build Coastguard Worker client_secret: String, client secret for the client_id. 174*800a58d9SAndroid Build Coastguard Worker user_agent: The user agent for the credential, e.g. "acloud" 175*800a58d9SAndroid Build Coastguard Worker scopes: String, scopes separated by space. 176*800a58d9SAndroid Build Coastguard Worker 177*800a58d9SAndroid Build Coastguard Worker Returns: 178*800a58d9SAndroid Build Coastguard Worker An oauth2client.OAuth2Credentials instance. 179*800a58d9SAndroid Build Coastguard Worker """ 180*800a58d9SAndroid Build Coastguard Worker if not client_id or not client_secret: 181*800a58d9SAndroid Build Coastguard Worker raise errors.AuthenticationError( 182*800a58d9SAndroid Build Coastguard Worker "Could not authenticate using Oauth2 flow, please set client_id " 183*800a58d9SAndroid Build Coastguard Worker "and client_secret in your config file. Contact the cloud project's " 184*800a58d9SAndroid Build Coastguard Worker "admin if you don't have the client_id and client_secret.") 185*800a58d9SAndroid Build Coastguard Worker storage = multistore_file.get_credential_storage( 186*800a58d9SAndroid Build Coastguard Worker filename=os.path.abspath(creds_cache_file), 187*800a58d9SAndroid Build Coastguard Worker client_id=client_id, 188*800a58d9SAndroid Build Coastguard Worker user_agent=user_agent, 189*800a58d9SAndroid Build Coastguard Worker scope=scopes) 190*800a58d9SAndroid Build Coastguard Worker credentials = storage.get() 191*800a58d9SAndroid Build Coastguard Worker if credentials is not None: 192*800a58d9SAndroid Build Coastguard Worker if not credentials.access_token_expired and not credentials.invalid: 193*800a58d9SAndroid Build Coastguard Worker return credentials 194*800a58d9SAndroid Build Coastguard Worker try: 195*800a58d9SAndroid Build Coastguard Worker credentials.refresh(httplib2.Http()) 196*800a58d9SAndroid Build Coastguard Worker except oauth2_client.AccessTokenRefreshError: 197*800a58d9SAndroid Build Coastguard Worker pass 198*800a58d9SAndroid Build Coastguard Worker if not credentials.invalid: 199*800a58d9SAndroid Build Coastguard Worker return credentials 200*800a58d9SAndroid Build Coastguard Worker return _RunAuthFlow(storage, client_id, client_secret, user_agent, scopes) 201*800a58d9SAndroid Build Coastguard Worker 202*800a58d9SAndroid Build Coastguard Worker 203*800a58d9SAndroid Build Coastguard Workerdef CreateCredentials(acloud_config, scopes=_ALL_SCOPES): 204*800a58d9SAndroid Build Coastguard Worker """Create credentials. 205*800a58d9SAndroid Build Coastguard Worker 206*800a58d9SAndroid Build Coastguard Worker If no specific scope provided, we create a full scopes credentials for 207*800a58d9SAndroid Build Coastguard Worker authenticating and user will only go oauth2 flow once after login with 208*800a58d9SAndroid Build Coastguard Worker full scopes credentials. 209*800a58d9SAndroid Build Coastguard Worker 210*800a58d9SAndroid Build Coastguard Worker Args: 211*800a58d9SAndroid Build Coastguard Worker acloud_config: An AcloudConfig object. 212*800a58d9SAndroid Build Coastguard Worker scopes: A string representing for scopes, separted by space, 213*800a58d9SAndroid Build Coastguard Worker like "SCOPE_1 SCOPE_2 SCOPE_3" 214*800a58d9SAndroid Build Coastguard Worker 215*800a58d9SAndroid Build Coastguard Worker Returns: 216*800a58d9SAndroid Build Coastguard Worker An oauth2client.OAuth2Credentials instance. 217*800a58d9SAndroid Build Coastguard Worker """ 218*800a58d9SAndroid Build Coastguard Worker if os.path.isabs(acloud_config.creds_cache_file): 219*800a58d9SAndroid Build Coastguard Worker creds_cache_file = acloud_config.creds_cache_file 220*800a58d9SAndroid Build Coastguard Worker else: 221*800a58d9SAndroid Build Coastguard Worker creds_cache_file = os.path.join(HOME_FOLDER, 222*800a58d9SAndroid Build Coastguard Worker acloud_config.creds_cache_file) 223*800a58d9SAndroid Build Coastguard Worker 224*800a58d9SAndroid Build Coastguard Worker if acloud_config.service_account_json_private_key_path: 225*800a58d9SAndroid Build Coastguard Worker return _CreateOauthServiceAccountCredsWithJsonKey( 226*800a58d9SAndroid Build Coastguard Worker acloud_config.service_account_json_private_key_path, 227*800a58d9SAndroid Build Coastguard Worker scopes=scopes, 228*800a58d9SAndroid Build Coastguard Worker creds_cache_file=creds_cache_file, 229*800a58d9SAndroid Build Coastguard Worker user_agent=acloud_config.user_agent) 230*800a58d9SAndroid Build Coastguard Worker if acloud_config.service_account_private_key_path: 231*800a58d9SAndroid Build Coastguard Worker return _CreateOauthServiceAccountCreds( 232*800a58d9SAndroid Build Coastguard Worker acloud_config.service_account_name, 233*800a58d9SAndroid Build Coastguard Worker acloud_config.service_account_private_key_path, 234*800a58d9SAndroid Build Coastguard Worker scopes=scopes) 235*800a58d9SAndroid Build Coastguard Worker 236*800a58d9SAndroid Build Coastguard Worker return _CreateOauthUserCreds( 237*800a58d9SAndroid Build Coastguard Worker creds_cache_file=creds_cache_file, 238*800a58d9SAndroid Build Coastguard Worker client_id=acloud_config.client_id, 239*800a58d9SAndroid Build Coastguard Worker client_secret=acloud_config.client_secret, 240*800a58d9SAndroid Build Coastguard Worker user_agent=acloud_config.user_agent, 241*800a58d9SAndroid Build Coastguard Worker scopes=scopes) 242