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