blob: 356780bf1d87addcdd80f44fa57bc503eb25bcdf [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
31import google.auth.transport._http_client
32from google.oauth2 import service_account
33import google.oauth2.credentials
34
35_LOGGER = logging.getLogger(__name__)
36
37# Valid types accepted for file-based credentials.
38_AUTHORIZED_USER_TYPE = 'authorized_user'
39_SERVICE_ACCOUNT_TYPE = 'service_account'
40_VALID_TYPES = (_AUTHORIZED_USER_TYPE, _SERVICE_ACCOUNT_TYPE)
41
42# Help message when no credentials can be found.
43_HELP_MESSAGE = """
44Could not automatically determine credentials. Please set {env} or
45explicitly create credential and re-run the application. For more
46information, please see
47https://developers.google.com/accounts/docs/application-default-credentials.
48""".format(env=environment_vars.CREDENTIALS).strip()
49
50
51def _load_credentials_from_file(filename):
52 """Loads credentials from a file.
53
54 The credentials file must be a service account key or stored authorized
55 user credentials.
56
57 Args:
58 filename (str): The full path to the credentials file.
59
60 Returns:
61 Tuple[google.auth.credentials.Credentials, Optional[str]]: Loaded
62 credentials and the project ID. Authorized user credentials do not
63 have the project ID information.
64
65 Raises:
66 google.auth.exceptions.DefaultCredentialsError: if the file is in the
67 wrong format.
68 """
69 with io.open(filename, 'r') as file_obj:
70 try:
71 info = json.load(file_obj)
72 except ValueError as exc:
73 raise exceptions.DefaultCredentialsError(
74 'File {} is not a valid json file.'.format(filename), exc)
75
76 # The type key should indicate that the file is either a service account
77 # credentials file or an authorized user credentials file.
78 credential_type = info.get('type')
79
80 if credential_type == _AUTHORIZED_USER_TYPE:
81 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:
91 try:
92 credentials = (
93 service_account.Credentials.from_service_account_info(info))
94 except ValueError as exc:
95 raise exceptions.DefaultCredentialsError(
96 'Failed to load service account credentials from {}'.format(
97 filename), exc)
98 return credentials, info.get('project_id')
99
100 else:
101 raise exceptions.DefaultCredentialsError(
102 'The file {file} does not have a valid type. '
103 'Type is {type}, expected one of {valid_types}.'.format(
104 file=filename, type=credential_type, valid_types=_VALID_TYPES))
105
106
107def _get_gcloud_sdk_credentials():
108 """Gets the credentials and project ID from the Cloud SDK."""
109 # 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 Parrott2148fde2016-10-24 13:44:25 -0700154 try:
155 credentials = app_engine.Credentials()
156 project_id = app_engine.get_project_id()
157 return credentials, project_id
158 except EnvironmentError:
159 return None, None
Jon Wayne Parrottaadb3de2016-10-19 09:34:05 -0700160
161
162def _get_gce_credentials(request=None):
163 """Gets credentials and project ID from the GCE Metadata Service."""
164 # Ping requires a transport, but we want application default credentials
165 # to require no arguments. So, we'll use the _http_client transport which
166 # uses http.client. This is only acceptable because the metadata server
167 # doesn't do SSL and never requires proxies.
168
169 if request is None:
170 request = google.auth.transport._http_client.Request()
171
172 if _metadata.ping(request=request):
173 # Get the project ID.
174 try:
Jon Wayne Parrott5b03ba12016-10-24 13:51:26 -0700175 project_id = _metadata.get_project_id(request=request)
Jon Wayne Parrottaadb3de2016-10-19 09:34:05 -0700176 except exceptions.TransportError:
177 _LOGGER.warning(
178 'No project ID could be determined from the Compute Engine '
179 'metadata service. Consider setting the %s environment '
180 'variable.', environment_vars.PROJECT)
181 project_id = None
182
183 return compute_engine.Credentials(), project_id
184 else:
185 return None, None
186
187
188def default(request=None):
189 """Gets the default credentials for the current environment.
190
191 `Application Default Credentials`_ provides an easy way to obtain
192 credentials to call Google APIs for server-to-server or local applications.
193 This function acquires credentials from the environment in the following
194 order:
195
196 1. If the environment variable ``GOOGLE_APPLICATION_CREDENTIALS`` is set
197 to the path of a valid service account JSON private key file, then it is
198 loaded and returned. The project ID returned is the project ID defined
199 in the service account file if available (some older files do not
200 contain project ID information).
201 2. If the `Google Cloud SDK`_ is installed and has application default
202 credentials set they are loaded and returned.
203
204 To enable application default credentials with the Cloud SDK run::
205
206 gcloud auth application-default login
207
208 If the Cloud SDK has an active project, the project ID is returned. The
209 active project can be set using::
210
211 gcloud config set project
212
213 3. If the application is running in the `App Engine standard environment`_
214 then the credentials and project ID from the `App Identity Service`_
215 are used.
216 4. If the application is running in `Compute Engine`_ or the
217 `App Engine flexible environment`_ then the credentials and project ID
218 are obtained from the `Metadata Service`_.
219 5. If no credentials are found,
220 :class:`~google.auth.exceptions.DefaultCredentialsError` will be raised.
221
222 .. _Application Default Credentials: https://developers.google.com\
223 /identity/protocols/application-default-credentials
224 .. _Google Cloud SDK: https://cloud.google.com/sdk
225 .. _App Engine standard environment: https://cloud.google.com/appengine
226 .. _App Identity Service: https://cloud.google.com/appengine/docs/python\
227 /appidentity/
228 .. _Compute Engine: https://cloud.google.com/compute
229 .. _App Engine flexible environment: https://cloud.google.com\
230 /appengine/flexible
231 .. _Metadata Service: https://cloud.google.com/compute/docs\
232 /storing-retrieving-metadata
233
234 Example::
235
236 import google.auth
237
238 credentials, project_id = google.auth.default()
239
240 Args:
241 request (google.auth.transport.Request): An object used to make
242 HTTP requests. This is used to detect whether the application
243 is running on Compute Engine. If not specified, then it will
244 use the standard library http client to make requests.
245
246 Returns:
247 Tuple[~google.auth.credentials.Credentials, Optional[str]]:
248 the current environment's credentials and project ID. Project ID
249 may be None, which indicates that the Project ID could not be
250 ascertained from the environment.
251
252 Raises:
253 ~google.auth.exceptions.DefaultCredentialsError:
254 If no credentials were found, or if the credentials found were
255 invalid.
256 """
257 explicit_project_id = os.environ.get(environment_vars.PROJECT)
258
259 checkers = (
260 _get_explicit_environ_credentials,
261 _get_gcloud_sdk_credentials,
262 _get_gae_credentials,
263 lambda: _get_gce_credentials(request))
264
265 for checker in checkers:
266 credentials, project_id = checker()
267 if credentials is not None:
268 return credentials, explicit_project_id or project_id
269
270 raise exceptions.DefaultCredentialsError(_HELP_MESSAGE)