blob: 819abc847d5ae005da2767313244f3b8686a139a [file] [log] [blame]
Tri Voa3141f52016-09-30 17:32:04 -07001#!/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 whitelisted by
36 Android Build team so that acloud can access build api.
37"""
38
39import logging
40import os
Tri Voa3141f52016-09-30 17:32:04 -070041
42import httplib2
43
Kevin Chenga9dac952018-07-11 10:42:02 -070044# pylint: disable=import-error
Tri Voa3141f52016-09-30 17:32:04 -070045from oauth2client import client as oauth2_client
Kevin Chenga9dac952018-07-11 10:42:02 -070046from oauth2client import service_account as oauth2_service_account
Tri Voa3141f52016-09-30 17:32:04 -070047from oauth2client.contrib import multistore_file
48from oauth2client import tools as oauth2_tools
49
Sam Chiu7de3b232018-12-06 19:45:52 +080050from acloud import errors
Tri Voa3141f52016-09-30 17:32:04 -070051
52logger = logging.getLogger(__name__)
53HOME_FOLDER = os.path.expanduser("~")
Sam Chiu4d9bb4b2018-10-26 11:38:23 +080054# If there is no specific scope use case, we will always use this default full
55# scopes to run CreateCredentials func and user will only go oauth2 flow once
56# after login with this full scopes credentials.
57_ALL_SCOPES = " ".join(["https://www.googleapis.com/auth/compute",
58 "https://www.googleapis.com/auth/logging.write",
59 "https://www.googleapis.com/auth/androidbuild.internal",
60 "https://www.googleapis.com/auth/devstorage.read_write",
61 "https://www.googleapis.com/auth/userinfo.email"])
Tri Voa3141f52016-09-30 17:32:04 -070062
63
64def _CreateOauthServiceAccountCreds(email, private_key_path, scopes):
65 """Create credentials with a normal service account.
66
67 Args:
68 email: email address as the account.
69 private_key_path: Path to the service account P12 key.
70 scopes: string, multiple scopes should be saperated by space.
71 Api scopes to request for the oauth token.
72
73 Returns:
74 An oauth2client.OAuth2Credentials instance.
75
76 Raises:
xingdai8a00d462018-07-30 14:24:48 -070077 errors.AuthenticationError: if failed to authenticate.
Tri Voa3141f52016-09-30 17:32:04 -070078 """
79 try:
Kevin Chenga9dac952018-07-11 10:42:02 -070080 credentials = oauth2_service_account.ServiceAccountCredentials.from_p12_keyfile(
81 email, private_key_path, scopes=scopes)
Tri Voa3141f52016-09-30 17:32:04 -070082 except EnvironmentError as e:
xingdai8a00d462018-07-30 14:24:48 -070083 raise errors.AuthenticationError(
Kevin Chenga9dac952018-07-11 10:42:02 -070084 "Could not authenticate using private key file (%s) "
85 " error message: %s" % (private_key_path, str(e)))
Tri Voa3141f52016-09-30 17:32:04 -070086 return credentials
87
Sam Chiu4d9bb4b2018-10-26 11:38:23 +080088# pylint: disable=invalid-name
xingdai8a00d462018-07-30 14:24:48 -070089def _CreateOauthServiceAccountCredsWithJsonKey(json_private_key_path, scopes):
90 """Create credentials with a normal service account from json key file.
91
92 Args:
93 json_private_key_path: Path to the service account json key file.
94 scopes: string, multiple scopes should be saperated by space.
95 Api scopes to request for the oauth token.
96
97 Returns:
98 An oauth2client.OAuth2Credentials instance.
99
100 Raises:
101 errors.AuthenticationError: if failed to authenticate.
102 """
103 try:
104 return (
105 oauth2_service_account.ServiceAccountCredentials
106 .from_json_keyfile_name(
107 json_private_key_path, scopes=scopes))
108 except EnvironmentError as e:
109 raise errors.AuthenticationError(
110 "Could not authenticate using json private key file (%s) "
111 " error message: %s" % (json_private_key_path, str(e)))
112
113
Tri Voa3141f52016-09-30 17:32:04 -0700114class RunFlowFlags(object):
115 """Flags for oauth2client.tools.run_flow."""
116
117 def __init__(self, browser_auth):
118 self.auth_host_port = [8080, 8090]
119 self.auth_host_name = "localhost"
120 self.logging_level = "ERROR"
121 self.noauth_local_webserver = not browser_auth
122
123
124def _RunAuthFlow(storage, client_id, client_secret, user_agent, scopes):
125 """Get user oauth2 credentials.
126
127 Args:
128 client_id: String, client id from the cloud project.
129 client_secret: String, client secret for the client_id.
130 user_agent: The user agent for the credential, e.g. "acloud"
131 scopes: String, scopes separated by space.
132
133 Returns:
134 An oauth2client.OAuth2Credentials instance.
135 """
136 flags = RunFlowFlags(browser_auth=False)
137 flow = oauth2_client.OAuth2WebServerFlow(
138 client_id=client_id,
139 client_secret=client_secret,
140 scope=scopes,
141 user_agent=user_agent)
142 credentials = oauth2_tools.run_flow(
143 flow=flow, storage=storage, flags=flags)
144 return credentials
145
146
147def _CreateOauthUserCreds(creds_cache_file, client_id, client_secret,
148 user_agent, scopes):
149 """Get user oauth2 credentials.
150
151 Args:
152 creds_cache_file: String, file name for the credential cache.
153 e.g. .acloud_oauth2.dat
154 Will be created at home folder.
155 client_id: String, client id from the cloud project.
156 client_secret: String, client secret for the client_id.
157 user_agent: The user agent for the credential, e.g. "acloud"
158 scopes: String, scopes separated by space.
159
160 Returns:
161 An oauth2client.OAuth2Credentials instance.
162 """
163 if not client_id or not client_secret:
xingdai8a00d462018-07-30 14:24:48 -0700164 raise errors.AuthenticationError(
Tri Voa3141f52016-09-30 17:32:04 -0700165 "Could not authenticate using Oauth2 flow, please set client_id "
166 "and client_secret in your config file. Contact the cloud project's "
167 "admin if you don't have the client_id and client_secret.")
168 storage = multistore_file.get_credential_storage(
169 filename=os.path.abspath(creds_cache_file),
170 client_id=client_id,
171 user_agent=user_agent,
172 scope=scopes)
173 credentials = storage.get()
174 if credentials is not None:
175 try:
176 credentials.refresh(httplib2.Http())
177 except oauth2_client.AccessTokenRefreshError:
178 pass
179 if not credentials.invalid:
180 return credentials
181 return _RunAuthFlow(storage, client_id, client_secret, user_agent, scopes)
182
183
Sam Chiu4d9bb4b2018-10-26 11:38:23 +0800184def CreateCredentials(acloud_config, scopes=_ALL_SCOPES):
Tri Voa3141f52016-09-30 17:32:04 -0700185 """Create credentials.
186
Sam Chiu4d9bb4b2018-10-26 11:38:23 +0800187 If no specific scope provided, we create a full scopes credentials for
188 authenticating and user will only go oauth2 flow once after login with
189 full scopes credentials.
190
Tri Voa3141f52016-09-30 17:32:04 -0700191 Args:
192 acloud_config: An AcloudConfig object.
193 scopes: A string representing for scopes, separted by space,
194 like "SCOPE_1 SCOPE_2 SCOPE_3"
195
196 Returns:
197 An oauth2client.OAuth2Credentials instance.
198 """
xingdai8a00d462018-07-30 14:24:48 -0700199 if acloud_config.service_account_json_private_key_path:
200 return _CreateOauthServiceAccountCredsWithJsonKey(
201 acloud_config.service_account_json_private_key_path,
202 scopes=scopes)
203 elif acloud_config.service_account_private_key_path:
Tri Voa3141f52016-09-30 17:32:04 -0700204 return _CreateOauthServiceAccountCreds(
205 acloud_config.service_account_name,
206 acloud_config.service_account_private_key_path,
207 scopes=scopes)
208
209 creds_cache_file = os.path.join(HOME_FOLDER,
210 acloud_config.creds_cache_file)
211 return _CreateOauthUserCreds(
212 creds_cache_file=creds_cache_file,
213 client_id=acloud_config.client_id,
214 client_secret=acloud_config.client_secret,
215 user_agent=acloud_config.user_agent,
216 scopes=scopes)