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