xref: /aosp_15_r20/tools/acloud/internal/lib/auth.py (revision 800a58d989c669b8eb8a71d8df53b1ba3d411444)
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