blob: 1e91368f5fa17213a879e42ac9cf4783ca61c332 [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
61 wrong format.
62 """
63 with io.open(filename, 'r') as file_obj:
64 try:
65 info = json.load(file_obj)
66 except ValueError as exc:
67 raise exceptions.DefaultCredentialsError(
68 'File {} is not a valid json file.'.format(filename), exc)
69
70 # The type key should indicate that the file is either a service account
71 # credentials file or an authorized user credentials file.
72 credential_type = info.get('type')
73
74 if credential_type == _AUTHORIZED_USER_TYPE:
Jon Wayne Parrott6dca98c2016-12-01 15:34:59 -080075 from google.auth import _cloud_sdk
76
Jon Wayne Parrottaadb3de2016-10-19 09:34:05 -070077 try:
78 credentials = _cloud_sdk.load_authorized_user_credentials(info)
79 except ValueError as exc:
80 raise exceptions.DefaultCredentialsError(
81 'Failed to load authorized user credentials from {}'.format(
82 filename), exc)
83 # Authorized user credentials do not contain the project ID.
84 return credentials, None
85
86 elif credential_type == _SERVICE_ACCOUNT_TYPE:
Jon Wayne Parrott6dca98c2016-12-01 15:34:59 -080087 from google.oauth2 import service_account
88
Jon Wayne Parrottaadb3de2016-10-19 09:34:05 -070089 try:
90 credentials = (
91 service_account.Credentials.from_service_account_info(info))
92 except ValueError as exc:
93 raise exceptions.DefaultCredentialsError(
94 'Failed to load service account credentials from {}'.format(
95 filename), exc)
96 return credentials, info.get('project_id')
97
98 else:
99 raise exceptions.DefaultCredentialsError(
100 'The file {file} does not have a valid type. '
101 'Type is {type}, expected one of {valid_types}.'.format(
102 file=filename, type=credential_type, valid_types=_VALID_TYPES))
103
104
105def _get_gcloud_sdk_credentials():
106 """Gets the credentials and project ID from the Cloud SDK."""
Jon Wayne Parrott6dca98c2016-12-01 15:34:59 -0800107 from google.auth import _cloud_sdk
108
Jon Wayne Parrottaadb3de2016-10-19 09:34:05 -0700109 # Check if application default credentials exist.
110 credentials_filename = (
111 _cloud_sdk.get_application_default_credentials_path())
112
113 if not os.path.isfile(credentials_filename):
114 return None, None
115
116 credentials, project_id = _load_credentials_from_file(
117 credentials_filename)
118
119 if not project_id:
120 project_id = _cloud_sdk.get_project_id()
121
122 if not project_id:
123 _LOGGER.warning(
124 'No project ID could be determined from the Cloud SDK '
125 'configuration. Consider running `gcloud config set project` or '
126 'setting the %s environment variable', environment_vars.PROJECT)
127
128 return credentials, project_id
129
130
131def _get_explicit_environ_credentials():
132 """Gets credentials from the GOOGLE_APPLICATION_CREDENTIALS environment
133 variable."""
134 explicit_file = os.environ.get(environment_vars.CREDENTIALS)
135
136 if explicit_file is not None:
137 credentials, project_id = _load_credentials_from_file(
138 os.environ[environment_vars.CREDENTIALS])
139
140 if not project_id:
141 _LOGGER.warning(
142 'No project ID could be determined from the credentials at %s '
143 'Consider setting the %s environment variable',
144 environment_vars.CREDENTIALS, environment_vars.PROJECT)
145
146 return credentials, project_id
147
148 else:
149 return None, None
150
151
152def _get_gae_credentials():
153 """Gets Google App Engine App Identity credentials and project ID."""
Jon Wayne Parrott6dca98c2016-12-01 15:34:59 -0800154 from google.auth import app_engine
155
Jon Wayne Parrott2148fde2016-10-24 13:44:25 -0700156 try:
157 credentials = app_engine.Credentials()
158 project_id = app_engine.get_project_id()
159 return credentials, project_id
160 except EnvironmentError:
161 return None, None
Jon Wayne Parrottaadb3de2016-10-19 09:34:05 -0700162
163
164def _get_gce_credentials(request=None):
165 """Gets credentials and project ID from the GCE Metadata Service."""
166 # Ping requires a transport, but we want application default credentials
167 # to require no arguments. So, we'll use the _http_client transport which
168 # uses http.client. This is only acceptable because the metadata server
169 # doesn't do SSL and never requires proxies.
Jon Wayne Parrott6dca98c2016-12-01 15:34:59 -0800170 from google.auth import compute_engine
171 from google.auth.compute_engine import _metadata
Jon Wayne Parrottaadb3de2016-10-19 09:34:05 -0700172
173 if request is None:
174 request = google.auth.transport._http_client.Request()
175
176 if _metadata.ping(request=request):
177 # Get the project ID.
178 try:
Jon Wayne Parrott5b03ba12016-10-24 13:51:26 -0700179 project_id = _metadata.get_project_id(request=request)
Jon Wayne Parrottaadb3de2016-10-19 09:34:05 -0700180 except exceptions.TransportError:
181 _LOGGER.warning(
182 'No project ID could be determined from the Compute Engine '
183 'metadata service. Consider setting the %s environment '
184 'variable.', environment_vars.PROJECT)
185 project_id = None
186
187 return compute_engine.Credentials(), project_id
188 else:
189 return None, None
190
191
Jon Wayne Parrott8a7e5062016-11-07 16:45:17 -0800192def default(scopes=None, request=None):
Jon Wayne Parrottaadb3de2016-10-19 09:34:05 -0700193 """Gets the default credentials for the current environment.
194
195 `Application Default Credentials`_ provides an easy way to obtain
196 credentials to call Google APIs for server-to-server or local applications.
197 This function acquires credentials from the environment in the following
198 order:
199
200 1. If the environment variable ``GOOGLE_APPLICATION_CREDENTIALS`` is set
201 to the path of a valid service account JSON private key file, then it is
202 loaded and returned. The project ID returned is the project ID defined
203 in the service account file if available (some older files do not
204 contain project ID information).
205 2. If the `Google Cloud SDK`_ is installed and has application default
206 credentials set they are loaded and returned.
207
208 To enable application default credentials with the Cloud SDK run::
209
210 gcloud auth application-default login
211
212 If the Cloud SDK has an active project, the project ID is returned. The
213 active project can be set using::
214
215 gcloud config set project
216
217 3. If the application is running in the `App Engine standard environment`_
218 then the credentials and project ID from the `App Identity Service`_
219 are used.
220 4. If the application is running in `Compute Engine`_ or the
221 `App Engine flexible environment`_ then the credentials and project ID
222 are obtained from the `Metadata Service`_.
223 5. If no credentials are found,
224 :class:`~google.auth.exceptions.DefaultCredentialsError` will be raised.
225
226 .. _Application Default Credentials: https://developers.google.com\
227 /identity/protocols/application-default-credentials
228 .. _Google Cloud SDK: https://cloud.google.com/sdk
229 .. _App Engine standard environment: https://cloud.google.com/appengine
230 .. _App Identity Service: https://cloud.google.com/appengine/docs/python\
231 /appidentity/
232 .. _Compute Engine: https://cloud.google.com/compute
233 .. _App Engine flexible environment: https://cloud.google.com\
234 /appengine/flexible
235 .. _Metadata Service: https://cloud.google.com/compute/docs\
236 /storing-retrieving-metadata
237
238 Example::
239
240 import google.auth
241
242 credentials, project_id = google.auth.default()
243
244 Args:
Jon Wayne Parrott8a7e5062016-11-07 16:45:17 -0800245 scopes (Sequence[str]): The list of scopes for the credentials. If
246 specified, the credentials will automatically be scoped if
247 necessary.
Jon Wayne Parrottaadb3de2016-10-19 09:34:05 -0700248 request (google.auth.transport.Request): An object used to make
249 HTTP requests. This is used to detect whether the application
250 is running on Compute Engine. If not specified, then it will
251 use the standard library http client to make requests.
252
253 Returns:
254 Tuple[~google.auth.credentials.Credentials, Optional[str]]:
255 the current environment's credentials and project ID. Project ID
256 may be None, which indicates that the Project ID could not be
257 ascertained from the environment.
258
259 Raises:
260 ~google.auth.exceptions.DefaultCredentialsError:
261 If no credentials were found, or if the credentials found were
262 invalid.
263 """
Jon Wayne Parrott6dca98c2016-12-01 15:34:59 -0800264 from google.auth.credentials import with_scopes_if_required
265
Jon Wayne Parrottce37cba2016-11-07 16:41:42 -0800266 explicit_project_id = os.environ.get(
267 environment_vars.PROJECT,
268 os.environ.get(environment_vars.LEGACY_PROJECT))
Jon Wayne Parrottaadb3de2016-10-19 09:34:05 -0700269
270 checkers = (
271 _get_explicit_environ_credentials,
272 _get_gcloud_sdk_credentials,
273 _get_gae_credentials,
274 lambda: _get_gce_credentials(request))
275
276 for checker in checkers:
277 credentials, project_id = checker()
278 if credentials is not None:
Jon Wayne Parrott6dca98c2016-12-01 15:34:59 -0800279 credentials = with_scopes_if_required(credentials, scopes)
Jon Wayne Parrottaadb3de2016-10-19 09:34:05 -0700280 return credentials, explicit_project_id or project_id
281
282 raise exceptions.DefaultCredentialsError(_HELP_MESSAGE)