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