• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
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 allowed by
36  Android Build team so that acloud can access build api.
37"""
38
39import logging
40import os
41
42import httplib2
43
44# pylint: disable=import-error
45from oauth2client import client as oauth2_client
46from oauth2client import service_account as oauth2_service_account
47from oauth2client.contrib import multistore_file
48from oauth2client import tools as oauth2_tools
49
50from acloud import errors
51
52
53logger = logging.getLogger(__name__)
54HOME_FOLDER = os.path.expanduser("~")
55# If there is no specific scope use case, we will always use this default full
56# scopes to run CreateCredentials func and user will only go oauth2 flow once
57# after login with this full scopes credentials.
58_ALL_SCOPES = " ".join(["https://www.googleapis.com/auth/compute",
59                        "https://www.googleapis.com/auth/logging.write",
60                        "https://www.googleapis.com/auth/androidbuild.internal",
61                        "https://www.googleapis.com/auth/devstorage.read_write",
62                        "https://www.googleapis.com/auth/userinfo.email"])
63
64
65def _CreateOauthServiceAccountCreds(email, private_key_path, scopes):
66    """Create credentials with a normal service account.
67
68    Args:
69        email: email address as the account.
70        private_key_path: Path to the service account P12 key.
71        scopes: string, multiple scopes should be saperated by space.
72                        Api scopes to request for the oauth token.
73
74    Returns:
75        An oauth2client.OAuth2Credentials instance.
76
77    Raises:
78        errors.AuthenticationError: if failed to authenticate.
79    """
80    try:
81        credentials = oauth2_service_account.ServiceAccountCredentials.from_p12_keyfile(
82            email, private_key_path, scopes=scopes)
83    except EnvironmentError as e:
84        raise errors.AuthenticationError(
85            "Could not authenticate using private key file (%s) "
86            " error message: %s" % (private_key_path, str(e)))
87    return credentials
88
89
90# pylint: disable=invalid-name
91def _CreateOauthServiceAccountCredsWithJsonKey(json_private_key_path, scopes,
92                                               creds_cache_file, user_agent):
93    """Create credentials with a normal service account from json key file.
94
95    Args:
96        json_private_key_path: Path to the service account json key file.
97        scopes: string, multiple scopes should be saperated by space.
98                        Api scopes to request for the oauth token.
99        creds_cache_file: String, file name for the credential cache.
100                          e.g. .acloud_oauth2.dat
101                          Will be created at home folder.
102        user_agent: String, the user agent for the credential, e.g. "acloud"
103
104    Returns:
105        An oauth2client.OAuth2Credentials instance.
106
107    Raises:
108        errors.AuthenticationError: if failed to authenticate.
109    """
110    try:
111        credentials = oauth2_service_account.ServiceAccountCredentials.from_json_keyfile_name(
112            json_private_key_path, scopes=scopes)
113        storage = multistore_file.get_credential_storage(
114            filename=os.path.abspath(creds_cache_file),
115            client_id=credentials.client_id,
116            user_agent=user_agent,
117            scope=scopes)
118        credentials.set_store(storage)
119    except EnvironmentError as e:
120        raise errors.AuthenticationError(
121            "Could not authenticate using json private key file (%s) "
122            " error message: %s" % (json_private_key_path, str(e)))
123
124    return credentials
125
126
127class RunFlowFlags():
128    """Flags for oauth2client.tools.run_flow."""
129
130    def __init__(self, browser_auth):
131        self.auth_host_port = [8080, 8090]
132        self.auth_host_name = "localhost"
133        self.logging_level = "ERROR"
134        self.noauth_local_webserver = not browser_auth
135
136
137def _RunAuthFlow(storage, client_id, client_secret, user_agent, scopes):
138    """Get user oauth2 credentials.
139
140    Args:
141        client_id: String, client id from the cloud project.
142        client_secret: String, client secret for the client_id.
143        user_agent: The user agent for the credential, e.g. "acloud"
144        scopes: String, scopes separated by space.
145
146    Returns:
147        An oauth2client.OAuth2Credentials instance.
148    """
149    flags = RunFlowFlags(browser_auth=False)
150    flow = oauth2_client.OAuth2WebServerFlow(
151        client_id=client_id,
152        client_secret=client_secret,
153        scope=scopes,
154        user_agent=user_agent)
155    credentials = oauth2_tools.run_flow(
156        flow=flow, storage=storage, flags=flags)
157    return credentials
158
159
160def _CreateOauthUserCreds(creds_cache_file, client_id, client_secret,
161                          user_agent, scopes):
162    """Get user oauth2 credentials.
163
164    Args:
165        creds_cache_file: String, file name for the credential cache.
166                                            e.g. .acloud_oauth2.dat
167                                            Will be created at home folder.
168        client_id: String, client id from the cloud project.
169        client_secret: String, client secret for the client_id.
170        user_agent: The user agent for the credential, e.g. "acloud"
171        scopes: String, scopes separated by space.
172
173    Returns:
174        An oauth2client.OAuth2Credentials instance.
175    """
176    if not client_id or not client_secret:
177        raise errors.AuthenticationError(
178            "Could not authenticate using Oauth2 flow, please set client_id "
179            "and client_secret in your config file. Contact the cloud project's "
180            "admin if you don't have the client_id and client_secret.")
181    storage = multistore_file.get_credential_storage(
182        filename=os.path.abspath(creds_cache_file),
183        client_id=client_id,
184        user_agent=user_agent,
185        scope=scopes)
186    credentials = storage.get()
187    if credentials is not None:
188        if not credentials.access_token_expired and not credentials.invalid:
189            return credentials
190        try:
191            credentials.refresh(httplib2.Http())
192        except oauth2_client.AccessTokenRefreshError:
193            pass
194        if not credentials.invalid:
195            return credentials
196    return _RunAuthFlow(storage, client_id, client_secret, user_agent, scopes)
197
198
199def CreateCredentials(acloud_config, scopes=_ALL_SCOPES):
200    """Create credentials.
201
202    If no specific scope provided, we create a full scopes credentials for
203    authenticating and user will only go oauth2 flow once after login with
204    full scopes credentials.
205
206    Args:
207        acloud_config: An AcloudConfig object.
208        scopes: A string representing for scopes, separted by space,
209            like "SCOPE_1 SCOPE_2 SCOPE_3"
210
211    Returns:
212        An oauth2client.OAuth2Credentials instance.
213    """
214    if os.path.isabs(acloud_config.creds_cache_file):
215        creds_cache_file = acloud_config.creds_cache_file
216    else:
217        creds_cache_file = os.path.join(HOME_FOLDER,
218                                        acloud_config.creds_cache_file)
219
220    if acloud_config.service_account_json_private_key_path:
221        return _CreateOauthServiceAccountCredsWithJsonKey(
222            acloud_config.service_account_json_private_key_path,
223            scopes=scopes,
224            creds_cache_file=creds_cache_file,
225            user_agent=acloud_config.user_agent)
226    if acloud_config.service_account_private_key_path:
227        return _CreateOauthServiceAccountCreds(
228            acloud_config.service_account_name,
229            acloud_config.service_account_private_key_path,
230            scopes=scopes)
231
232    return _CreateOauthUserCreds(
233        creds_cache_file=creds_cache_file,
234        client_id=acloud_config.client_id,
235        client_secret=acloud_config.client_secret,
236        user_agent=acloud_config.user_agent,
237        scopes=scopes)
238