blob: 7dac642e20ab38da64fb4d1adfdd8ab0bb301111 [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
Jon Wayne Parrottaadb3de2016-10-19 09:34:05 -070025from google.auth import environment_vars
26from google.auth import exceptions
Jon Wayne Parrottaadb3de2016-10-19 09:34:05 -070027import google.auth.transport._http_client
Jon Wayne Parrottaadb3de2016-10-19 09:34:05 -070028
29_LOGGER = logging.getLogger(__name__)
30
31# Valid types accepted for file-based credentials.
32_AUTHORIZED_USER_TYPE = 'authorized_user'
33_SERVICE_ACCOUNT_TYPE = 'service_account'
34_VALID_TYPES = (_AUTHORIZED_USER_TYPE, _SERVICE_ACCOUNT_TYPE)
35
36# Help message when no credentials can be found.
37_HELP_MESSAGE = """
38Could not automatically determine credentials. Please set {env} or
39explicitly create credential and re-run the application. For more
40information, please see
41https://developers.google.com/accounts/docs/application-default-credentials.
42""".format(env=environment_vars.CREDENTIALS).strip()
43
44
45def _load_credentials_from_file(filename):
46 """Loads credentials from a file.
47
48 The credentials file must be a service account key or stored authorized
49 user credentials.
50
51 Args:
52 filename (str): The full path to the credentials file.
53
54 Returns:
55 Tuple[google.auth.credentials.Credentials, Optional[str]]: Loaded
56 credentials and the project ID. Authorized user credentials do not
57 have the project ID information.
58
59 Raises:
60 google.auth.exceptions.DefaultCredentialsError: if the file is in the
weitaiting6e86c932017-08-12 03:26:59 +080061 wrong format or is missing.
Jon Wayne Parrottaadb3de2016-10-19 09:34:05 -070062 """
weitaiting6e86c932017-08-12 03:26:59 +080063 if not os.path.exists(filename):
64 raise exceptions.DefaultCredentialsError(
65 'File {} was not found.'.format(filename))
66
Jon Wayne Parrottaadb3de2016-10-19 09:34:05 -070067 with io.open(filename, 'r') as file_obj:
68 try:
69 info = json.load(file_obj)
70 except ValueError as exc:
71 raise exceptions.DefaultCredentialsError(
72 'File {} is not a valid json file.'.format(filename), exc)
73
74 # The type key should indicate that the file is either a service account
75 # credentials file or an authorized user credentials file.
76 credential_type = info.get('type')
77
78 if credential_type == _AUTHORIZED_USER_TYPE:
Jon Wayne Parrott6dca98c2016-12-01 15:34:59 -080079 from google.auth import _cloud_sdk
80
Jon Wayne Parrottaadb3de2016-10-19 09:34:05 -070081 try:
82 credentials = _cloud_sdk.load_authorized_user_credentials(info)
83 except ValueError as exc:
84 raise exceptions.DefaultCredentialsError(
85 'Failed to load authorized user credentials from {}'.format(
86 filename), exc)
87 # Authorized user credentials do not contain the project ID.
88 return credentials, None
89
90 elif credential_type == _SERVICE_ACCOUNT_TYPE:
Jon Wayne Parrott6dca98c2016-12-01 15:34:59 -080091 from google.oauth2 import service_account
92
Jon Wayne Parrottaadb3de2016-10-19 09:34:05 -070093 try:
94 credentials = (
95 service_account.Credentials.from_service_account_info(info))
96 except ValueError as exc:
97 raise exceptions.DefaultCredentialsError(
98 'Failed to load service account credentials from {}'.format(
99 filename), exc)
100 return credentials, info.get('project_id')
101
102 else:
103 raise exceptions.DefaultCredentialsError(
104 'The file {file} does not have a valid type. '
105 'Type is {type}, expected one of {valid_types}.'.format(
106 file=filename, type=credential_type, valid_types=_VALID_TYPES))
107
108
109def _get_gcloud_sdk_credentials():
110 """Gets the credentials and project ID from the Cloud SDK."""
Jon Wayne Parrott6dca98c2016-12-01 15:34:59 -0800111 from google.auth import _cloud_sdk
112
Jon Wayne Parrottaadb3de2016-10-19 09:34:05 -0700113 # Check if application default credentials exist.
114 credentials_filename = (
115 _cloud_sdk.get_application_default_credentials_path())
116
117 if not os.path.isfile(credentials_filename):
118 return None, None
119
120 credentials, project_id = _load_credentials_from_file(
121 credentials_filename)
122
123 if not project_id:
124 project_id = _cloud_sdk.get_project_id()
125
126 if not project_id:
127 _LOGGER.warning(
128 'No project ID could be determined from the Cloud SDK '
129 'configuration. Consider running `gcloud config set project` or '
130 'setting the %s environment variable', environment_vars.PROJECT)
131
132 return credentials, project_id
133
134
135def _get_explicit_environ_credentials():
136 """Gets credentials from the GOOGLE_APPLICATION_CREDENTIALS environment
137 variable."""
138 explicit_file = os.environ.get(environment_vars.CREDENTIALS)
139
140 if explicit_file is not None:
141 credentials, project_id = _load_credentials_from_file(
142 os.environ[environment_vars.CREDENTIALS])
143
144 if not project_id:
145 _LOGGER.warning(
146 'No project ID could be determined from the credentials at %s '
147 'Consider setting the %s environment variable',
148 environment_vars.CREDENTIALS, environment_vars.PROJECT)
149
150 return credentials, project_id
151
152 else:
153 return None, None
154
155
156def _get_gae_credentials():
157 """Gets Google App Engine App Identity credentials and project ID."""
Jon Wayne Parrott6dca98c2016-12-01 15:34:59 -0800158 from google.auth import app_engine
159
Jon Wayne Parrott2148fde2016-10-24 13:44:25 -0700160 try:
161 credentials = app_engine.Credentials()
162 project_id = app_engine.get_project_id()
163 return credentials, project_id
164 except EnvironmentError:
165 return None, None
Jon Wayne Parrottaadb3de2016-10-19 09:34:05 -0700166
167
168def _get_gce_credentials(request=None):
169 """Gets credentials and project ID from the GCE Metadata Service."""
170 # Ping requires a transport, but we want application default credentials
171 # to require no arguments. So, we'll use the _http_client transport which
172 # uses http.client. This is only acceptable because the metadata server
173 # doesn't do SSL and never requires proxies.
Jon Wayne Parrott6dca98c2016-12-01 15:34:59 -0800174 from google.auth import compute_engine
175 from google.auth.compute_engine import _metadata
Jon Wayne Parrottaadb3de2016-10-19 09:34:05 -0700176
177 if request is None:
178 request = google.auth.transport._http_client.Request()
179
180 if _metadata.ping(request=request):
181 # Get the project ID.
182 try:
Jon Wayne Parrott5b03ba12016-10-24 13:51:26 -0700183 project_id = _metadata.get_project_id(request=request)
Jon Wayne Parrottaadb3de2016-10-19 09:34:05 -0700184 except exceptions.TransportError:
185 _LOGGER.warning(
186 'No project ID could be determined from the Compute Engine '
187 'metadata service. Consider setting the %s environment '
188 'variable.', environment_vars.PROJECT)
189 project_id = None
190
191 return compute_engine.Credentials(), project_id
192 else:
193 return None, None
194
195
Jon Wayne Parrott8a7e5062016-11-07 16:45:17 -0800196def default(scopes=None, request=None):
Jon Wayne Parrottaadb3de2016-10-19 09:34:05 -0700197 """Gets the default credentials for the current environment.
198
199 `Application Default Credentials`_ provides an easy way to obtain
200 credentials to call Google APIs for server-to-server or local applications.
201 This function acquires credentials from the environment in the following
202 order:
203
204 1. If the environment variable ``GOOGLE_APPLICATION_CREDENTIALS`` is set
205 to the path of a valid service account JSON private key file, then it is
206 loaded and returned. The project ID returned is the project ID defined
207 in the service account file if available (some older files do not
208 contain project ID information).
209 2. If the `Google Cloud SDK`_ is installed and has application default
210 credentials set they are loaded and returned.
211
212 To enable application default credentials with the Cloud SDK run::
213
214 gcloud auth application-default login
215
216 If the Cloud SDK has an active project, the project ID is returned. The
217 active project can be set using::
218
219 gcloud config set project
220
221 3. If the application is running in the `App Engine standard environment`_
222 then the credentials and project ID from the `App Identity Service`_
223 are used.
224 4. If the application is running in `Compute Engine`_ or the
225 `App Engine flexible environment`_ then the credentials and project ID
226 are obtained from the `Metadata Service`_.
227 5. If no credentials are found,
228 :class:`~google.auth.exceptions.DefaultCredentialsError` will be raised.
229
230 .. _Application Default Credentials: https://developers.google.com\
231 /identity/protocols/application-default-credentials
232 .. _Google Cloud SDK: https://cloud.google.com/sdk
233 .. _App Engine standard environment: https://cloud.google.com/appengine
234 .. _App Identity Service: https://cloud.google.com/appengine/docs/python\
235 /appidentity/
236 .. _Compute Engine: https://cloud.google.com/compute
237 .. _App Engine flexible environment: https://cloud.google.com\
238 /appengine/flexible
239 .. _Metadata Service: https://cloud.google.com/compute/docs\
240 /storing-retrieving-metadata
241
242 Example::
243
244 import google.auth
245
246 credentials, project_id = google.auth.default()
247
248 Args:
Jon Wayne Parrott8a7e5062016-11-07 16:45:17 -0800249 scopes (Sequence[str]): The list of scopes for the credentials. If
250 specified, the credentials will automatically be scoped if
251 necessary.
Jon Wayne Parrottaadb3de2016-10-19 09:34:05 -0700252 request (google.auth.transport.Request): An object used to make
253 HTTP requests. This is used to detect whether the application
254 is running on Compute Engine. If not specified, then it will
255 use the standard library http client to make requests.
256
257 Returns:
258 Tuple[~google.auth.credentials.Credentials, Optional[str]]:
259 the current environment's credentials and project ID. Project ID
260 may be None, which indicates that the Project ID could not be
261 ascertained from the environment.
262
263 Raises:
264 ~google.auth.exceptions.DefaultCredentialsError:
265 If no credentials were found, or if the credentials found were
266 invalid.
267 """
Jon Wayne Parrott6dca98c2016-12-01 15:34:59 -0800268 from google.auth.credentials import with_scopes_if_required
269
Jon Wayne Parrottce37cba2016-11-07 16:41:42 -0800270 explicit_project_id = os.environ.get(
271 environment_vars.PROJECT,
272 os.environ.get(environment_vars.LEGACY_PROJECT))
Jon Wayne Parrottaadb3de2016-10-19 09:34:05 -0700273
274 checkers = (
275 _get_explicit_environ_credentials,
276 _get_gcloud_sdk_credentials,
277 _get_gae_credentials,
278 lambda: _get_gce_credentials(request))
279
280 for checker in checkers:
281 credentials, project_id = checker()
282 if credentials is not None:
Jon Wayne Parrott6dca98c2016-12-01 15:34:59 -0800283 credentials = with_scopes_if_required(credentials, scopes)
Jon Wayne Parrottaadb3de2016-10-19 09:34:05 -0700284 return credentials, explicit_project_id or project_id
285
286 raise exceptions.DefaultCredentialsError(_HELP_MESSAGE)