Tri Vo | a3141f5 | 2016-09-30 17:32:04 -0700 | [diff] [blame] | 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 | |
| 18 | Possible cases of authentication are noted below. |
| 19 | |
| 20 | -------------------------------------------------------- |
| 21 | account | authentcation |
| 22 | -------------------------------------------------------- |
| 23 | |
| 24 | google account (e.g. gmail)* | normal oauth2 |
| 25 | |
| 26 | |
| 27 | service 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 | |
| 39 | import logging |
| 40 | import os |
Tri Vo | a3141f5 | 2016-09-30 17:32:04 -0700 | [diff] [blame] | 41 | |
| 42 | import httplib2 |
| 43 | |
Kevin Cheng | a9dac95 | 2018-07-11 10:42:02 -0700 | [diff] [blame^] | 44 | # pylint: disable=import-error |
Tri Vo | a3141f5 | 2016-09-30 17:32:04 -0700 | [diff] [blame] | 45 | from oauth2client import client as oauth2_client |
Kevin Cheng | a9dac95 | 2018-07-11 10:42:02 -0700 | [diff] [blame^] | 46 | from oauth2client import service_account as oauth2_service_account |
Tri Vo | a3141f5 | 2016-09-30 17:32:04 -0700 | [diff] [blame] | 47 | from oauth2client.contrib import multistore_file |
| 48 | from oauth2client import tools as oauth2_tools |
| 49 | |
| 50 | from acloud.public import errors |
| 51 | |
| 52 | logger = logging.getLogger(__name__) |
| 53 | HOME_FOLDER = os.path.expanduser("~") |
| 54 | |
| 55 | |
| 56 | def _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: |
| 69 | errors.AuthentcationError: if failed to authenticate. |
| 70 | """ |
| 71 | try: |
Kevin Cheng | a9dac95 | 2018-07-11 10:42:02 -0700 | [diff] [blame^] | 72 | credentials = oauth2_service_account.ServiceAccountCredentials.from_p12_keyfile( |
| 73 | email, private_key_path, scopes=scopes) |
Tri Vo | a3141f5 | 2016-09-30 17:32:04 -0700 | [diff] [blame] | 74 | except EnvironmentError as e: |
| 75 | raise errors.AuthentcationError( |
Kevin Cheng | a9dac95 | 2018-07-11 10:42:02 -0700 | [diff] [blame^] | 76 | "Could not authenticate using private key file (%s) " |
| 77 | " error message: %s" % (private_key_path, str(e))) |
Tri Vo | a3141f5 | 2016-09-30 17:32:04 -0700 | [diff] [blame] | 78 | return credentials |
| 79 | |
| 80 | |
| 81 | class RunFlowFlags(object): |
| 82 | """Flags for oauth2client.tools.run_flow.""" |
| 83 | |
| 84 | def __init__(self, browser_auth): |
| 85 | self.auth_host_port = [8080, 8090] |
| 86 | self.auth_host_name = "localhost" |
| 87 | self.logging_level = "ERROR" |
| 88 | self.noauth_local_webserver = not browser_auth |
| 89 | |
| 90 | |
| 91 | def _RunAuthFlow(storage, client_id, client_secret, user_agent, scopes): |
| 92 | """Get user oauth2 credentials. |
| 93 | |
| 94 | Args: |
| 95 | client_id: String, client id from the cloud project. |
| 96 | client_secret: String, client secret for the client_id. |
| 97 | user_agent: The user agent for the credential, e.g. "acloud" |
| 98 | scopes: String, scopes separated by space. |
| 99 | |
| 100 | Returns: |
| 101 | An oauth2client.OAuth2Credentials instance. |
| 102 | """ |
| 103 | flags = RunFlowFlags(browser_auth=False) |
| 104 | flow = oauth2_client.OAuth2WebServerFlow( |
| 105 | client_id=client_id, |
| 106 | client_secret=client_secret, |
| 107 | scope=scopes, |
| 108 | user_agent=user_agent) |
| 109 | credentials = oauth2_tools.run_flow( |
| 110 | flow=flow, storage=storage, flags=flags) |
| 111 | return credentials |
| 112 | |
| 113 | |
| 114 | def _CreateOauthUserCreds(creds_cache_file, client_id, client_secret, |
| 115 | user_agent, scopes): |
| 116 | """Get user oauth2 credentials. |
| 117 | |
| 118 | Args: |
| 119 | creds_cache_file: String, file name for the credential cache. |
| 120 | e.g. .acloud_oauth2.dat |
| 121 | Will be created at home folder. |
| 122 | client_id: String, client id from the cloud project. |
| 123 | client_secret: String, client secret for the client_id. |
| 124 | user_agent: The user agent for the credential, e.g. "acloud" |
| 125 | scopes: String, scopes separated by space. |
| 126 | |
| 127 | Returns: |
| 128 | An oauth2client.OAuth2Credentials instance. |
| 129 | """ |
| 130 | if not client_id or not client_secret: |
| 131 | raise errors.AuthentcationError( |
| 132 | "Could not authenticate using Oauth2 flow, please set client_id " |
| 133 | "and client_secret in your config file. Contact the cloud project's " |
| 134 | "admin if you don't have the client_id and client_secret.") |
| 135 | storage = multistore_file.get_credential_storage( |
| 136 | filename=os.path.abspath(creds_cache_file), |
| 137 | client_id=client_id, |
| 138 | user_agent=user_agent, |
| 139 | scope=scopes) |
| 140 | credentials = storage.get() |
| 141 | if credentials is not None: |
| 142 | try: |
| 143 | credentials.refresh(httplib2.Http()) |
| 144 | except oauth2_client.AccessTokenRefreshError: |
| 145 | pass |
| 146 | if not credentials.invalid: |
| 147 | return credentials |
| 148 | return _RunAuthFlow(storage, client_id, client_secret, user_agent, scopes) |
| 149 | |
| 150 | |
| 151 | def CreateCredentials(acloud_config, scopes): |
| 152 | """Create credentials. |
| 153 | |
| 154 | Args: |
| 155 | acloud_config: An AcloudConfig object. |
| 156 | scopes: A string representing for scopes, separted by space, |
| 157 | like "SCOPE_1 SCOPE_2 SCOPE_3" |
| 158 | |
| 159 | Returns: |
| 160 | An oauth2client.OAuth2Credentials instance. |
| 161 | """ |
| 162 | if acloud_config.service_account_private_key_path: |
| 163 | return _CreateOauthServiceAccountCreds( |
| 164 | acloud_config.service_account_name, |
| 165 | acloud_config.service_account_private_key_path, |
| 166 | scopes=scopes) |
| 167 | |
| 168 | creds_cache_file = os.path.join(HOME_FOLDER, |
| 169 | acloud_config.creds_cache_file) |
| 170 | return _CreateOauthUserCreds( |
| 171 | creds_cache_file=creds_cache_file, |
| 172 | client_id=acloud_config.client_id, |
| 173 | client_secret=acloud_config.client_secret, |
| 174 | user_agent=acloud_config.user_agent, |
| 175 | scopes=scopes) |