blob: f9829d44e914bae40f8d2d22660598c88fec1b32 [file] [log] [blame]
Jon Wayne Parrottaadb3de2016-10-19 09:34:05 -07001# Copyright 2015 Google Inc.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15"""Application default credentials.
16
17Implements application default credentials and project ID detection.
18"""
19
20import io
21import json
22import logging
23import os
24
25from google.auth import _cloud_sdk
Jon Wayne Parrott2148fde2016-10-24 13:44:25 -070026from google.auth import app_engine
Jon Wayne Parrottaadb3de2016-10-19 09:34:05 -070027from google.auth import compute_engine
28from google.auth import environment_vars
29from google.auth import exceptions
30from google.auth.compute_engine import _metadata
Jon Wayne Parrott8a7e5062016-11-07 16:45:17 -080031import google.auth.credentials
Jon Wayne Parrottaadb3de2016-10-19 09:34:05 -070032import google.auth.transport._http_client
33from google.oauth2 import service_account
34import google.oauth2.credentials
35
36_LOGGER = logging.getLogger(__name__)
37
38# Valid types accepted for file-based credentials.
39_AUTHORIZED_USER_TYPE = 'authorized_user'
40_SERVICE_ACCOUNT_TYPE = 'service_account'
41_VALID_TYPES = (_AUTHORIZED_USER_TYPE, _SERVICE_ACCOUNT_TYPE)
42
43# Help message when no credentials can be found.
44_HELP_MESSAGE = """
45Could not automatically determine credentials. Please set {env} or
46explicitly create credential and re-run the application. For more
47information, please see
48https://developers.google.com/accounts/docs/application-default-credentials.
49""".format(env=environment_vars.CREDENTIALS).strip()
50
51
52def _load_credentials_from_file(filename):
53 """Loads credentials from a file.
54
55 The credentials file must be a service account key or stored authorized
56 user credentials.
57
58 Args:
59 filename (str): The full path to the credentials file.
60
61 Returns:
62 Tuple[google.auth.credentials.Credentials, Optional[str]]: Loaded
63 credentials and the project ID. Authorized user credentials do not
64 have the project ID information.
65
66 Raises:
67 google.auth.exceptions.DefaultCredentialsError: if the file is in the
68 wrong format.
69 """
70 with io.open(filename, 'r') as file_obj:
71 try:
72 info = json.load(file_obj)
73 except ValueError as exc:
74 raise exceptions.DefaultCredentialsError(
75 'File {} is not a valid json file.'.format(filename), exc)
76
77 # The type key should indicate that the file is either a service account
78 # credentials file or an authorized user credentials file.
79 credential_type = info.get('type')
80
81 if credential_type == _AUTHORIZED_USER_TYPE:
82 try:
83 credentials = _cloud_sdk.load_authorized_user_credentials(info)
84 except ValueError as exc:
85 raise exceptions.DefaultCredentialsError(
86 'Failed to load authorized user credentials from {}'.format(
87 filename), exc)
88 # Authorized user credentials do not contain the project ID.
89 return credentials, None
90
91 elif credential_type == _SERVICE_ACCOUNT_TYPE:
92 try:
93 credentials = (
94 service_account.Credentials.from_service_account_info(info))
95 except ValueError as exc:
96 raise exceptions.DefaultCredentialsError(
97 'Failed to load service account credentials from {}'.format(
98 filename), exc)
99 return credentials, info.get('project_id')
100
101 else:
102 raise exceptions.DefaultCredentialsError(
103 'The file {file} does not have a valid type. '
104 'Type is {type}, expected one of {valid_types}.'.format(
105 file=filename, type=credential_type, valid_types=_VALID_TYPES))
106
107
108def _get_gcloud_sdk_credentials():
109 """Gets the credentials and project ID from the Cloud SDK."""
110 # Check if application default credentials exist.
111 credentials_filename = (
112 _cloud_sdk.get_application_default_credentials_path())
113
114 if not os.path.isfile(credentials_filename):
115 return None, None
116
117 credentials, project_id = _load_credentials_from_file(
118 credentials_filename)
119
120 if not project_id:
121 project_id = _cloud_sdk.get_project_id()
122
123 if not project_id:
124 _LOGGER.warning(
125 'No project ID could be determined from the Cloud SDK '
126 'configuration. Consider running `gcloud config set project` or '
127 'setting the %s environment variable', environment_vars.PROJECT)
128
129 return credentials, project_id
130
131
132def _get_explicit_environ_credentials():
133 """Gets credentials from the GOOGLE_APPLICATION_CREDENTIALS environment
134 variable."""
135 explicit_file = os.environ.get(environment_vars.CREDENTIALS)
136
137 if explicit_file is not None:
138 credentials, project_id = _load_credentials_from_file(
139 os.environ[environment_vars.CREDENTIALS])
140
141 if not project_id:
142 _LOGGER.warning(
143 'No project ID could be determined from the credentials at %s '
144 'Consider setting the %s environment variable',
145 environment_vars.CREDENTIALS, environment_vars.PROJECT)
146
147 return credentials, project_id
148
149 else:
150 return None, None
151
152
153def _get_gae_credentials():
154 """Gets Google App Engine App Identity credentials and project ID."""
Jon Wayne Parrott2148fde2016-10-24 13:44:25 -0700155 try:
156 credentials = app_engine.Credentials()
157 project_id = app_engine.get_project_id()
158 return credentials, project_id
159 except EnvironmentError:
160 return None, None
Jon Wayne Parrottaadb3de2016-10-19 09:34:05 -0700161
162
163def _get_gce_credentials(request=None):
164 """Gets credentials and project ID from the GCE Metadata Service."""
165 # Ping requires a transport, but we want application default credentials
166 # to require no arguments. So, we'll use the _http_client transport which
167 # uses http.client. This is only acceptable because the metadata server
168 # doesn't do SSL and never requires proxies.
169
170 if request is None:
171 request = google.auth.transport._http_client.Request()
172
173 if _metadata.ping(request=request):
174 # Get the project ID.
175 try:
Jon Wayne Parrott5b03ba12016-10-24 13:51:26 -0700176 project_id = _metadata.get_project_id(request=request)
Jon Wayne Parrottaadb3de2016-10-19 09:34:05 -0700177 except exceptions.TransportError:
178 _LOGGER.warning(
179 'No project ID could be determined from the Compute Engine '
180 'metadata service. Consider setting the %s environment '
181 'variable.', environment_vars.PROJECT)
182 project_id = None
183
184 return compute_engine.Credentials(), project_id
185 else:
186 return None, None
187
188
Jon Wayne Parrott8a7e5062016-11-07 16:45:17 -0800189def default(scopes=None, request=None):
Jon Wayne Parrottaadb3de2016-10-19 09:34:05 -0700190 """Gets the default credentials for the current environment.
191
192 `Application Default Credentials`_ provides an easy way to obtain
193 credentials to call Google APIs for server-to-server or local applications.
194 This function acquires credentials from the environment in the following
195 order:
196
197 1. If the environment variable ``GOOGLE_APPLICATION_CREDENTIALS`` is set
198 to the path of a valid service account JSON private key file, then it is
199 loaded and returned. The project ID returned is the project ID defined
200 in the service account file if available (some older files do not
201 contain project ID information).
202 2. If the `Google Cloud SDK`_ is installed and has application default
203 credentials set they are loaded and returned.
204
205 To enable application default credentials with the Cloud SDK run::
206
207 gcloud auth application-default login
208
209 If the Cloud SDK has an active project, the project ID is returned. The
210 active project can be set using::
211
212 gcloud config set project
213
214 3. If the application is running in the `App Engine standard environment`_
215 then the credentials and project ID from the `App Identity Service`_
216 are used.
217 4. If the application is running in `Compute Engine`_ or the
218 `App Engine flexible environment`_ then the credentials and project ID
219 are obtained from the `Metadata Service`_.
220 5. If no credentials are found,
221 :class:`~google.auth.exceptions.DefaultCredentialsError` will be raised.
222
223 .. _Application Default Credentials: https://developers.google.com\
224 /identity/protocols/application-default-credentials
225 .. _Google Cloud SDK: https://cloud.google.com/sdk
226 .. _App Engine standard environment: https://cloud.google.com/appengine
227 .. _App Identity Service: https://cloud.google.com/appengine/docs/python\
228 /appidentity/
229 .. _Compute Engine: https://cloud.google.com/compute
230 .. _App Engine flexible environment: https://cloud.google.com\
231 /appengine/flexible
232 .. _Metadata Service: https://cloud.google.com/compute/docs\
233 /storing-retrieving-metadata
234
235 Example::
236
237 import google.auth
238
239 credentials, project_id = google.auth.default()
240
241 Args:
Jon Wayne Parrott8a7e5062016-11-07 16:45:17 -0800242 scopes (Sequence[str]): The list of scopes for the credentials. If
243 specified, the credentials will automatically be scoped if
244 necessary.
Jon Wayne Parrottaadb3de2016-10-19 09:34:05 -0700245 request (google.auth.transport.Request): An object used to make
246 HTTP requests. This is used to detect whether the application
247 is running on Compute Engine. If not specified, then it will
248 use the standard library http client to make requests.
249
250 Returns:
251 Tuple[~google.auth.credentials.Credentials, Optional[str]]:
252 the current environment's credentials and project ID. Project ID
253 may be None, which indicates that the Project ID could not be
254 ascertained from the environment.
255
256 Raises:
257 ~google.auth.exceptions.DefaultCredentialsError:
258 If no credentials were found, or if the credentials found were
259 invalid.
260 """
Jon Wayne Parrottce37cba2016-11-07 16:41:42 -0800261 explicit_project_id = os.environ.get(
262 environment_vars.PROJECT,
263 os.environ.get(environment_vars.LEGACY_PROJECT))
Jon Wayne Parrottaadb3de2016-10-19 09:34:05 -0700264
265 checkers = (
266 _get_explicit_environ_credentials,
267 _get_gcloud_sdk_credentials,
268 _get_gae_credentials,
269 lambda: _get_gce_credentials(request))
270
271 for checker in checkers:
272 credentials, project_id = checker()
273 if credentials is not None:
Jon Wayne Parrott8a7e5062016-11-07 16:45:17 -0800274 credentials = google.auth.credentials.with_scopes_if_required(
275 credentials, scopes)
Jon Wayne Parrottaadb3de2016-10-19 09:34:05 -0700276 return credentials, explicit_project_id or project_id
277
278 raise exceptions.DefaultCredentialsError(_HELP_MESSAGE)