| #!/usr/bin/env python |
| # |
| # Copyright 2016 - The Android Open Source Project |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| """Module for handling Authentication. |
| |
| Possible cases of authentication are noted below. |
| |
| -------------------------------------------------------- |
| account | authentcation |
| -------------------------------------------------------- |
| |
| google account (e.g. gmail)* | normal oauth2 |
| |
| |
| service account* | oauth2 + private key |
| |
| -------------------------------------------------------- |
| |
| * For now, non-google employees (i.e. non @google.com account) or |
| non-google-owned service account can not access Android Build API. |
| Only local build artifact can be used. |
| |
| * Google-owned service account, if used, needs to be whitelisted by |
| Android Build team so that acloud can access build api. |
| """ |
| |
| import logging |
| import os |
| |
| import httplib2 |
| |
| # pylint: disable=import-error |
| from oauth2client import client as oauth2_client |
| from oauth2client import service_account as oauth2_service_account |
| from oauth2client.contrib import multistore_file |
| from oauth2client import tools as oauth2_tools |
| |
| from acloud.public import errors |
| |
| logger = logging.getLogger(__name__) |
| HOME_FOLDER = os.path.expanduser("~") |
| # If there is no specific scope use case, we will always use this default full |
| # scopes to run CreateCredentials func and user will only go oauth2 flow once |
| # after login with this full scopes credentials. |
| _ALL_SCOPES = " ".join(["https://www.googleapis.com/auth/compute", |
| "https://www.googleapis.com/auth/logging.write", |
| "https://www.googleapis.com/auth/androidbuild.internal", |
| "https://www.googleapis.com/auth/devstorage.read_write", |
| "https://www.googleapis.com/auth/userinfo.email"]) |
| |
| |
| def _CreateOauthServiceAccountCreds(email, private_key_path, scopes): |
| """Create credentials with a normal service account. |
| |
| Args: |
| email: email address as the account. |
| private_key_path: Path to the service account P12 key. |
| scopes: string, multiple scopes should be saperated by space. |
| Api scopes to request for the oauth token. |
| |
| Returns: |
| An oauth2client.OAuth2Credentials instance. |
| |
| Raises: |
| errors.AuthenticationError: if failed to authenticate. |
| """ |
| try: |
| credentials = oauth2_service_account.ServiceAccountCredentials.from_p12_keyfile( |
| email, private_key_path, scopes=scopes) |
| except EnvironmentError as e: |
| raise errors.AuthenticationError( |
| "Could not authenticate using private key file (%s) " |
| " error message: %s" % (private_key_path, str(e))) |
| return credentials |
| |
| # pylint: disable=invalid-name |
| def _CreateOauthServiceAccountCredsWithJsonKey(json_private_key_path, scopes): |
| """Create credentials with a normal service account from json key file. |
| |
| Args: |
| json_private_key_path: Path to the service account json key file. |
| scopes: string, multiple scopes should be saperated by space. |
| Api scopes to request for the oauth token. |
| |
| Returns: |
| An oauth2client.OAuth2Credentials instance. |
| |
| Raises: |
| errors.AuthenticationError: if failed to authenticate. |
| """ |
| try: |
| return ( |
| oauth2_service_account.ServiceAccountCredentials |
| .from_json_keyfile_name( |
| json_private_key_path, scopes=scopes)) |
| except EnvironmentError as e: |
| raise errors.AuthenticationError( |
| "Could not authenticate using json private key file (%s) " |
| " error message: %s" % (json_private_key_path, str(e))) |
| |
| |
| class RunFlowFlags(object): |
| """Flags for oauth2client.tools.run_flow.""" |
| |
| def __init__(self, browser_auth): |
| self.auth_host_port = [8080, 8090] |
| self.auth_host_name = "localhost" |
| self.logging_level = "ERROR" |
| self.noauth_local_webserver = not browser_auth |
| |
| |
| def _RunAuthFlow(storage, client_id, client_secret, user_agent, scopes): |
| """Get user oauth2 credentials. |
| |
| Args: |
| client_id: String, client id from the cloud project. |
| client_secret: String, client secret for the client_id. |
| user_agent: The user agent for the credential, e.g. "acloud" |
| scopes: String, scopes separated by space. |
| |
| Returns: |
| An oauth2client.OAuth2Credentials instance. |
| """ |
| flags = RunFlowFlags(browser_auth=False) |
| flow = oauth2_client.OAuth2WebServerFlow( |
| client_id=client_id, |
| client_secret=client_secret, |
| scope=scopes, |
| user_agent=user_agent) |
| credentials = oauth2_tools.run_flow( |
| flow=flow, storage=storage, flags=flags) |
| return credentials |
| |
| |
| def _CreateOauthUserCreds(creds_cache_file, client_id, client_secret, |
| user_agent, scopes): |
| """Get user oauth2 credentials. |
| |
| Args: |
| creds_cache_file: String, file name for the credential cache. |
| e.g. .acloud_oauth2.dat |
| Will be created at home folder. |
| client_id: String, client id from the cloud project. |
| client_secret: String, client secret for the client_id. |
| user_agent: The user agent for the credential, e.g. "acloud" |
| scopes: String, scopes separated by space. |
| |
| Returns: |
| An oauth2client.OAuth2Credentials instance. |
| """ |
| if not client_id or not client_secret: |
| raise errors.AuthenticationError( |
| "Could not authenticate using Oauth2 flow, please set client_id " |
| "and client_secret in your config file. Contact the cloud project's " |
| "admin if you don't have the client_id and client_secret.") |
| storage = multistore_file.get_credential_storage( |
| filename=os.path.abspath(creds_cache_file), |
| client_id=client_id, |
| user_agent=user_agent, |
| scope=scopes) |
| credentials = storage.get() |
| if credentials is not None: |
| try: |
| credentials.refresh(httplib2.Http()) |
| except oauth2_client.AccessTokenRefreshError: |
| pass |
| if not credentials.invalid: |
| return credentials |
| return _RunAuthFlow(storage, client_id, client_secret, user_agent, scopes) |
| |
| |
| def CreateCredentials(acloud_config, scopes=_ALL_SCOPES): |
| """Create credentials. |
| |
| If no specific scope provided, we create a full scopes credentials for |
| authenticating and user will only go oauth2 flow once after login with |
| full scopes credentials. |
| |
| Args: |
| acloud_config: An AcloudConfig object. |
| scopes: A string representing for scopes, separted by space, |
| like "SCOPE_1 SCOPE_2 SCOPE_3" |
| |
| Returns: |
| An oauth2client.OAuth2Credentials instance. |
| """ |
| if acloud_config.service_account_json_private_key_path: |
| return _CreateOauthServiceAccountCredsWithJsonKey( |
| acloud_config.service_account_json_private_key_path, |
| scopes=scopes) |
| elif acloud_config.service_account_private_key_path: |
| return _CreateOauthServiceAccountCreds( |
| acloud_config.service_account_name, |
| acloud_config.service_account_private_key_path, |
| scopes=scopes) |
| |
| creds_cache_file = os.path.join(HOME_FOLDER, |
| acloud_config.creds_cache_file) |
| return _CreateOauthUserCreds( |
| creds_cache_file=creds_cache_file, |
| client_id=acloud_config.client_id, |
| client_secret=acloud_config.client_secret, |
| user_agent=acloud_config.user_agent, |
| scopes=scopes) |