3
# Copyright 2007 Google Inc.
5
# Licensed under the Apache License, Version 2.0 (the "License");
6
# you may not use this file except in compliance with the License.
7
# You may obtain a copy of the License at
9
# http://www.apache.org/licenses/LICENSE-2.0
11
# Unless required by applicable law or agreed to in writing, software
12
# distributed under the License is distributed on an "AS IS" BASIS,
13
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
# See the License for the specific language governing permissions and
15
# limitations under the License.
22
"""Provides access functions for the app identity service."""
33
from google.appengine.api import apiproxy_stub_map
34
from google.appengine.api import memcache
35
from google.appengine.api.app_identity import app_identity_service_pb
36
from google.appengine.runtime import apiproxy_errors
38
__all__ = ['BackendDeadlineExceeded',
44
'make_sign_blob_call',
45
'make_get_public_certificates_call',
46
'make_get_service_account_name_call',
48
'get_public_certificates',
50
'get_service_account_name',
52
'get_default_version_hostname',
54
'get_access_token_uncached',
55
'make_get_access_token_call',
59
_APP_IDENTITY_SERVICE_NAME = 'app_identity_service'
60
_SIGN_FOR_APP_METHOD_NAME = 'SignForApp'
61
_GET_CERTS_METHOD_NAME = 'GetPublicCertificatesForApp'
62
_GET_SERVICE_ACCOUNT_NAME_METHOD_NAME = 'GetServiceAccountName'
63
_GET_ACCESS_TOKEN_METHOD_NAME = 'GetAccessToken'
64
_PARTITION_SEPARATOR = '~'
65
_DOMAIN_SEPARATOR = ':'
66
_MEMCACHE_KEY_PREFIX = '_ah_app_identity_'
67
_MEMCACHE_NAMESPACE = '_ah_'
70
class Error(Exception):
71
"""Base error type."""
74
class BackendDeadlineExceeded(Error):
75
"""Communication to backend service timed-out."""
78
class BlobSizeTooLarge(Error):
79
"""Size of blob to sign is larger than the allowed limit."""
82
class InternalError(Error):
83
"""Unspecified internal failure."""
86
class InvalidScope(Error):
90
def _to_app_identity_error(error):
91
"""Translate an application error to an external Error, if possible.
94
error: An ApplicationError to translate.
97
error: app identity API specific error message.
100
app_identity_service_pb.AppIdentityServiceError.NOT_A_VALID_APP:
102
app_identity_service_pb.AppIdentityServiceError.DEADLINE_EXCEEDED:
103
BackendDeadlineExceeded,
104
app_identity_service_pb.AppIdentityServiceError.BLOB_TOO_LARGE:
106
app_identity_service_pb.AppIdentityServiceError.UNKNOWN_ERROR:
108
app_identity_service_pb.AppIdentityServiceError.UNKNOWN_SCOPE:
111
if error.application_error in error_map:
112
return error_map[error.application_error](error.error_detail)
117
class PublicCertificate(object):
118
"""Info about public certificate.
121
key_name: name of the certificate.
122
x509_certificate_pem: x509 cerficiates in pem format.
125
def __init__(self, key_name, x509_certificate_pem):
126
self.key_name = key_name
127
self.x509_certificate_pem = x509_certificate_pem
130
def create_rpc(deadline=None, callback=None):
131
"""Creates an RPC object for use with the App identity API.
134
deadline: Optional deadline in seconds for the operation; the default
135
is a system-specific deadline (typically 5 seconds).
136
callback: Optional callable to invoke on completion.
139
An apiproxy_stub_map.UserRPC object specialized for this service.
141
return apiproxy_stub_map.UserRPC(_APP_IDENTITY_SERVICE_NAME,
145
def make_sign_blob_call(rpc, bytes_to_sign):
146
"""Executes the RPC call to sign a blob.
149
rpc: a UserRPC instance.
150
bytes_to_sign: blob that needs to be signed.
153
A tuple that contains the signing key name and the signature.
156
TypeError: when bytes_to_sign is not a str.
158
if not isinstance(bytes_to_sign, str):
159
raise TypeError('bytes_to_sign must be str: %s'
161
request = app_identity_service_pb.SignForAppRequest()
162
request.set_bytes_to_sign(bytes_to_sign)
163
response = app_identity_service_pb.SignForAppResponse()
165
def signing_for_app_result(rpc):
166
"""Check success, handle exceptions, and return converted RPC result.
168
This method waits for the RPC if it has not yet finished, and calls the
169
post-call hooks on the first invocation.
172
rpc: A UserRPC object.
175
A tuple that contains signing key name and signature.
177
assert rpc.service == _APP_IDENTITY_SERVICE_NAME, repr(rpc.service)
178
assert rpc.method == _SIGN_FOR_APP_METHOD_NAME, repr(rpc.method)
181
except apiproxy_errors.ApplicationError, err:
182
raise _to_app_identity_error(err)
184
return (response.key_name(), response.signature_bytes())
187
rpc.make_call(_SIGN_FOR_APP_METHOD_NAME, request,
188
response, signing_for_app_result)
191
def make_get_public_certificates_call(rpc):
192
"""Executes the RPC call to get a list of public certificates.
195
rpc: a UserRPC instance.
198
A list of PublicCertificate object.
200
request = app_identity_service_pb.GetPublicCertificateForAppRequest()
201
response = app_identity_service_pb.GetPublicCertificateForAppResponse()
203
def get_certs_result(rpc):
204
"""Check success, handle exceptions, and return converted RPC result.
206
This method waits for the RPC if it has not yet finished, and calls the
207
post-call hooks on the first invocation.
210
rpc: A UserRPC object.
213
A list of PublicCertificate object.
215
assert rpc.service == _APP_IDENTITY_SERVICE_NAME, repr(rpc.service)
216
assert rpc.method == _GET_CERTS_METHOD_NAME, repr(rpc.method)
219
except apiproxy_errors.ApplicationError, err:
220
raise _to_app_identity_error(err)
222
for cert in response.public_certificate_list_list():
223
result.append(PublicCertificate(
224
cert.key_name(), cert.x509_certificate_pem()))
228
rpc.make_call(_GET_CERTS_METHOD_NAME, request, response, get_certs_result)
231
def make_get_service_account_name_call(rpc):
232
"""Get service account name of the app.
235
deadline: Optional deadline in seconds for the operation; the default
236
is a system-specific deadline (typically 5 seconds).
239
Service account name of the app.
241
request = app_identity_service_pb.GetServiceAccountNameRequest()
242
response = app_identity_service_pb.GetServiceAccountNameResponse()
244
if rpc.deadline is not None:
245
request.set_deadline(rpc.deadline)
247
def get_service_account_name_result(rpc):
248
"""Check success, handle exceptions, and return converted RPC result.
250
This method waits for the RPC if it has not yet finished, and calls the
251
post-call hooks on the first invocation.
254
rpc: A UserRPC object.
257
A string which is service account name of the app.
259
assert rpc.service == _APP_IDENTITY_SERVICE_NAME, repr(rpc.service)
260
assert rpc.method == _GET_SERVICE_ACCOUNT_NAME_METHOD_NAME, repr(rpc.method)
263
except apiproxy_errors.ApplicationError, err:
264
raise _to_app_identity_error(err)
266
return response.service_account_name()
269
rpc.make_call(_GET_SERVICE_ACCOUNT_NAME_METHOD_NAME, request,
270
response, get_service_account_name_result)
273
def sign_blob(bytes_to_sign, deadline=None):
277
bytes_to_sign: blob that needs to be signed.
278
deadline: Optional deadline in seconds for the operation; the default
279
is a system-specific deadline (typically 5 seconds).
282
Tuple, signing key name and signature.
284
rpc = create_rpc(deadline)
285
make_sign_blob_call(rpc, bytes_to_sign)
287
return rpc.get_result()
290
def get_public_certificates(deadline=None):
291
"""Get public certificates.
294
deadline: Optional deadline in seconds for the operation; the default
295
is a system-specific deadline (typically 5 seconds).
298
A list of PublicCertificate object.
300
rpc = create_rpc(deadline)
301
make_get_public_certificates_call(rpc)
303
return rpc.get_result()
306
def get_service_account_name(deadline=None):
307
"""Get service account name of the app.
310
deadline: Optional deadline in seconds for the operation; the default
311
is a system-specific deadline (typically 5 seconds).
314
Service account name of the app.
316
rpc = create_rpc(deadline)
317
make_get_service_account_name_call(rpc)
319
return rpc.get_result()
322
def _ParseFullAppId(app_id):
323
"""Parse a full app id into partition, domain name and display app_id.
326
app_id: The full partitioned app id.
329
A tuple (partition, domain_name, display_app_id). The partition
330
and domain name may be empty.
333
psep = app_id.find(_PARTITION_SEPARATOR)
335
partition = app_id[:psep]
336
app_id = app_id[psep+1:]
338
dsep = app_id.find(_DOMAIN_SEPARATOR)
340
domain_name = app_id[:dsep]
341
app_id = app_id[dsep+1:]
342
return partition, domain_name, app_id
345
def get_application_id():
346
"""Get the application id of an app.
349
The application id of the app.
351
full_app_id = os.getenv('APPLICATION_ID')
352
_, domain_name, display_app_id = _ParseFullAppId(full_app_id)
354
return '%s%s%s' % (domain_name, _DOMAIN_SEPARATOR, display_app_id)
355
return display_app_id
358
def get_default_version_hostname():
359
"""Get the standard hostname of the default version of the app.
361
For example if your application_id is my-app then the result might be
365
The standard hostname of the default version of the application.
372
return os.getenv('DEFAULT_VERSION_HOSTNAME')
375
def make_get_access_token_call(rpc, scopes):
376
"""OAuth2 access token to act on behalf of the application (async, uncached).
378
Most developers should use get_access_token instead.
382
scopes: The requested API scope string, or a list of strings.
384
InvalidScope: if the scopes are unspecified or invalid.
387
request = app_identity_service_pb.GetAccessTokenRequest()
389
raise InvalidScope('No scopes specified.')
390
if isinstance(scopes, basestring):
391
request.add_scope(scopes)
394
request.add_scope(scope)
395
response = app_identity_service_pb.GetAccessTokenResponse()
397
def get_access_token_result(rpc):
398
"""Check success, handle exceptions, and return converted RPC result.
400
This method waits for the RPC if it has not yet finished, and calls the
401
post-call hooks on the first invocation.
404
rpc: A UserRPC object.
407
Pair, Access token (string) and expiration time (seconds since the epoch).
409
assert rpc.service == _APP_IDENTITY_SERVICE_NAME, repr(rpc.service)
410
assert rpc.method == _GET_ACCESS_TOKEN_METHOD_NAME, repr(rpc.method)
413
except apiproxy_errors.ApplicationError, err:
414
raise _to_app_identity_error(err)
416
return response.access_token(), response.expiration_time()
419
rpc.make_call(_GET_ACCESS_TOKEN_METHOD_NAME, request,
420
response, get_access_token_result)
423
def get_access_token_uncached(scopes, deadline=None):
424
"""OAuth2 access token to act on behalf of the application (sync, uncached).
426
Most developers should use get_access_token instead.
429
scopes: The requested API scope string, or a list of strings.
430
deadline: Optional deadline in seconds for the operation; the default
431
is a system-specific deadline (typically 5 seconds).
433
Pair, Access token (string) and expiration time (seconds since the epoch).
435
rpc = create_rpc(deadline)
436
make_get_access_token_call(rpc, scopes)
438
return rpc.get_result()
441
def get_access_token(scopes):
442
"""OAuth2 access token to act on behalf of the application, cached.
444
Generates and caches an OAuth2 access token for the service account for the
445
appengine application.
447
Each application has an associated Google account. This function returns
448
OAuth2 access token corresponding to the running app. Access tokens are safe
449
to cache and reuse until their expiry time as returned. This method will
450
do that using memcache.
453
scopes: The requested API scope string, or a list of strings.
455
Pair, Access token (string) and expiration time (seconds since the epoch).
458
memcache_key = _MEMCACHE_KEY_PREFIX + str(scopes)
459
memcache_value = memcache.get(memcache_key, namespace=_MEMCACHE_NAMESPACE)
461
access_token, expires_at = memcache_value
463
access_token, expires_at = get_access_token_uncached(scopes)
465
memcache.add(memcache_key, (access_token, expires_at), expires_at - 300,
466
namespace=_MEMCACHE_NAMESPACE)
467
return access_token, expires_at