blob: 09cfcbac0d45da3dff76cb613ac7f20bcee469b1 [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
50from acloud.public import errors
51
52logger = logging.getLogger(__name__)
53HOME_FOLDER = os.path.expanduser("~")
54
55
56def _CreateOauthServiceAccountCreds(email, private_key_path, scopes):
57 """Create credentials with a normal service account.
58
59 Args:
60 email: email address as the account.
61 private_key_path: Path to the service account P12 key.
62 scopes: string, multiple scopes should be saperated by space.
63 Api scopes to request for the oauth token.
64
65 Returns:
66 An oauth2client.OAuth2Credentials instance.
67
68 Raises:
xingdai8a00d462018-07-30 14:24:48 -070069 errors.AuthenticationError: if failed to authenticate.
Tri Voa3141f52016-09-30 17:32:04 -070070 """
71 try:
Kevin Chenga9dac952018-07-11 10:42:02 -070072 credentials = oauth2_service_account.ServiceAccountCredentials.from_p12_keyfile(
73 email, private_key_path, scopes=scopes)
Tri Voa3141f52016-09-30 17:32:04 -070074 except EnvironmentError as e:
xingdai8a00d462018-07-30 14:24:48 -070075 raise errors.AuthenticationError(
Kevin Chenga9dac952018-07-11 10:42:02 -070076 "Could not authenticate using private key file (%s) "
77 " error message: %s" % (private_key_path, str(e)))
Tri Voa3141f52016-09-30 17:32:04 -070078 return credentials
79
80
xingdai8a00d462018-07-30 14:24:48 -070081def _CreateOauthServiceAccountCredsWithJsonKey(json_private_key_path, scopes):
82 """Create credentials with a normal service account from json key file.
83
84 Args:
85 json_private_key_path: Path to the service account json key file.
86 scopes: string, multiple scopes should be saperated by space.
87 Api scopes to request for the oauth token.
88
89 Returns:
90 An oauth2client.OAuth2Credentials instance.
91
92 Raises:
93 errors.AuthenticationError: if failed to authenticate.
94 """
95 try:
96 return (
97 oauth2_service_account.ServiceAccountCredentials
98 .from_json_keyfile_name(
99 json_private_key_path, scopes=scopes))
100 except EnvironmentError as e:
101 raise errors.AuthenticationError(
102 "Could not authenticate using json private key file (%s) "
103 " error message: %s" % (json_private_key_path, str(e)))
104
105
Tri Voa3141f52016-09-30 17:32:04 -0700106class RunFlowFlags(object):
107 """Flags for oauth2client.tools.run_flow."""
108
109 def __init__(self, browser_auth):
110 self.auth_host_port = [8080, 8090]
111 self.auth_host_name = "localhost"
112 self.logging_level = "ERROR"
113 self.noauth_local_webserver = not browser_auth
114
115
116def _RunAuthFlow(storage, client_id, client_secret, user_agent, scopes):
117 """Get user oauth2 credentials.
118
119 Args:
120 client_id: String, client id from the cloud project.
121 client_secret: String, client secret for the client_id.
122 user_agent: The user agent for the credential, e.g. "acloud"
123 scopes: String, scopes separated by space.
124
125 Returns:
126 An oauth2client.OAuth2Credentials instance.
127 """
128 flags = RunFlowFlags(browser_auth=False)
129 flow = oauth2_client.OAuth2WebServerFlow(
130 client_id=client_id,
131 client_secret=client_secret,
132 scope=scopes,
133 user_agent=user_agent)
134 credentials = oauth2_tools.run_flow(
135 flow=flow, storage=storage, flags=flags)
136 return credentials
137
138
139def _CreateOauthUserCreds(creds_cache_file, client_id, client_secret,
140 user_agent, scopes):
141 """Get user oauth2 credentials.
142
143 Args:
144 creds_cache_file: String, file name for the credential cache.
145 e.g. .acloud_oauth2.dat
146 Will be created at home folder.
147 client_id: String, client id from the cloud project.
148 client_secret: String, client secret for the client_id.
149 user_agent: The user agent for the credential, e.g. "acloud"
150 scopes: String, scopes separated by space.
151
152 Returns:
153 An oauth2client.OAuth2Credentials instance.
154 """
155 if not client_id or not client_secret:
xingdai8a00d462018-07-30 14:24:48 -0700156 raise errors.AuthenticationError(
Tri Voa3141f52016-09-30 17:32:04 -0700157 "Could not authenticate using Oauth2 flow, please set client_id "
158 "and client_secret in your config file. Contact the cloud project's "
159 "admin if you don't have the client_id and client_secret.")
160 storage = multistore_file.get_credential_storage(
161 filename=os.path.abspath(creds_cache_file),
162 client_id=client_id,
163 user_agent=user_agent,
164 scope=scopes)
165 credentials = storage.get()
166 if credentials is not None:
167 try:
168 credentials.refresh(httplib2.Http())
169 except oauth2_client.AccessTokenRefreshError:
170 pass
171 if not credentials.invalid:
172 return credentials
173 return _RunAuthFlow(storage, client_id, client_secret, user_agent, scopes)
174
175
176def CreateCredentials(acloud_config, scopes):
177 """Create credentials.
178
179 Args:
180 acloud_config: An AcloudConfig object.
181 scopes: A string representing for scopes, separted by space,
182 like "SCOPE_1 SCOPE_2 SCOPE_3"
183
184 Returns:
185 An oauth2client.OAuth2Credentials instance.
186 """
xingdai8a00d462018-07-30 14:24:48 -0700187 if acloud_config.service_account_json_private_key_path:
188 return _CreateOauthServiceAccountCredsWithJsonKey(
189 acloud_config.service_account_json_private_key_path,
190 scopes=scopes)
191 elif acloud_config.service_account_private_key_path:
Tri Voa3141f52016-09-30 17:32:04 -0700192 return _CreateOauthServiceAccountCreds(
193 acloud_config.service_account_name,
194 acloud_config.service_account_private_key_path,
195 scopes=scopes)
196
197 creds_cache_file = os.path.join(HOME_FOLDER,
198 acloud_config.creds_cache_file)
199 return _CreateOauthUserCreds(
200 creds_cache_file=creds_cache_file,
201 client_id=acloud_config.client_id,
202 client_secret=acloud_config.client_secret,
203 user_agent=acloud_config.user_agent,
204 scopes=scopes)