~nchohan/appscale/zk3.3.4

« back to all changes in this revision

Viewing changes to AppServer/google/appengine/api/app_identity/app_identity.py

  • Committer: Chris Bunch
  • Date: 2012-02-17 08:19:21 UTC
  • mfrom: (787.2.3 appscale-raj-merge)
  • Revision ID: cgb@cs.ucsb.edu-20120217081921-pakidyksaenlpzur
merged with main branch, gaining rabbitmq and upgrades for hbase, cassandra, and hypertable, as well as upgrading to gae 1.6.1 for python and go

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/env python
 
2
#
 
3
# Copyright 2007 Google Inc.
 
4
#
 
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
 
8
#
 
9
#     http://www.apache.org/licenses/LICENSE-2.0
 
10
#
 
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.
 
16
#
 
17
 
 
18
 
 
19
 
 
20
 
 
21
 
 
22
"""Provides access functions for the app identity service."""
 
23
 
 
24
 
 
25
 
 
26
 
 
27
 
 
28
 
 
29
 
 
30
 
 
31
import os
 
32
 
 
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
 
37
 
 
38
__all__ = ['BackendDeadlineExceeded',
 
39
           'BlobSizeTooLarge',
 
40
           'InternalError',
 
41
           'InvalidScope',
 
42
           'Error',
 
43
           'create_rpc',
 
44
           'make_sign_blob_call',
 
45
           'make_get_public_certificates_call',
 
46
           'make_get_service_account_name_call',
 
47
           'sign_blob',
 
48
           'get_public_certificates',
 
49
           'PublicCertificate',
 
50
           'get_service_account_name',
 
51
           'get_application_id',
 
52
           'get_default_version_hostname',
 
53
           'get_access_token',
 
54
           'get_access_token_uncached',
 
55
           'make_get_access_token_call',
 
56
          ]
 
57
 
 
58
 
 
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_'
 
68
 
 
69
 
 
70
class Error(Exception):
 
71
  """Base error type."""
 
72
 
 
73
 
 
74
class BackendDeadlineExceeded(Error):
 
75
  """Communication to backend service timed-out."""
 
76
 
 
77
 
 
78
class BlobSizeTooLarge(Error):
 
79
  """Size of blob to sign is larger than the allowed limit."""
 
80
 
 
81
 
 
82
class InternalError(Error):
 
83
  """Unspecified internal failure."""
 
84
 
 
85
 
 
86
class InvalidScope(Error):
 
87
  """Invalid scope."""
 
88
 
 
89
 
 
90
def _to_app_identity_error(error):
 
91
  """Translate an application error to an external Error, if possible.
 
92
 
 
93
  Args:
 
94
    error: An ApplicationError to translate.
 
95
 
 
96
  Returns:
 
97
    error: app identity API specific error message.
 
98
  """
 
99
  error_map = {
 
100
      app_identity_service_pb.AppIdentityServiceError.NOT_A_VALID_APP:
 
101
      InternalError,
 
102
      app_identity_service_pb.AppIdentityServiceError.DEADLINE_EXCEEDED:
 
103
      BackendDeadlineExceeded,
 
104
      app_identity_service_pb.AppIdentityServiceError.BLOB_TOO_LARGE:
 
105
      BlobSizeTooLarge,
 
106
      app_identity_service_pb.AppIdentityServiceError.UNKNOWN_ERROR:
 
107
      InternalError,
 
108
      app_identity_service_pb.AppIdentityServiceError.UNKNOWN_SCOPE:
 
109
      InvalidScope,
 
110
      }
 
111
  if error.application_error in error_map:
 
112
    return error_map[error.application_error](error.error_detail)
 
113
  else:
 
114
    return error
 
115
 
 
116
 
 
117
class PublicCertificate(object):
 
118
  """Info about public certificate.
 
119
 
 
120
  Attributes:
 
121
    key_name: name of the certificate.
 
122
    x509_certificate_pem: x509 cerficiates in pem format.
 
123
  """
 
124
 
 
125
  def __init__(self, key_name, x509_certificate_pem):
 
126
    self.key_name = key_name
 
127
    self.x509_certificate_pem = x509_certificate_pem
 
128
 
 
129
 
 
130
def create_rpc(deadline=None, callback=None):
 
131
  """Creates an RPC object for use with the App identity API.
 
132
 
 
133
  Args:
 
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.
 
137
 
 
138
  Returns:
 
139
    An apiproxy_stub_map.UserRPC object specialized for this service.
 
140
  """
 
141
  return apiproxy_stub_map.UserRPC(_APP_IDENTITY_SERVICE_NAME,
 
142
                                   deadline, callback)
 
143
 
 
144
 
 
145
def make_sign_blob_call(rpc, bytes_to_sign):
 
146
  """Executes the RPC call to sign a blob.
 
147
 
 
148
  Args:
 
149
    rpc: a UserRPC instance.
 
150
    bytes_to_sign: blob that needs to be signed.
 
151
 
 
152
  Returns:
 
153
   A tuple that contains the signing key name and the signature.
 
154
 
 
155
  Raises:
 
156
    TypeError: when bytes_to_sign is not a str.
 
157
  """
 
158
  if not isinstance(bytes_to_sign, str):
 
159
    raise TypeError('bytes_to_sign must be str: %s'
 
160
                    % bytes_to_sign)
 
161
  request = app_identity_service_pb.SignForAppRequest()
 
162
  request.set_bytes_to_sign(bytes_to_sign)
 
163
  response = app_identity_service_pb.SignForAppResponse()
 
164
 
 
165
  def signing_for_app_result(rpc):
 
166
    """Check success, handle exceptions, and return converted RPC result.
 
167
 
 
168
    This method waits for the RPC if it has not yet finished, and calls the
 
169
    post-call hooks on the first invocation.
 
170
 
 
171
    Args:
 
172
      rpc: A UserRPC object.
 
173
 
 
174
    Returns:
 
175
      A tuple that contains signing key name and signature.
 
176
    """
 
177
    assert rpc.service == _APP_IDENTITY_SERVICE_NAME, repr(rpc.service)
 
178
    assert rpc.method == _SIGN_FOR_APP_METHOD_NAME, repr(rpc.method)
 
179
    try:
 
180
      rpc.check_success()
 
181
    except apiproxy_errors.ApplicationError, err:
 
182
      raise _to_app_identity_error(err)
 
183
 
 
184
    return (response.key_name(), response.signature_bytes())
 
185
 
 
186
 
 
187
  rpc.make_call(_SIGN_FOR_APP_METHOD_NAME, request,
 
188
                response, signing_for_app_result)
 
189
 
 
190
 
 
191
def make_get_public_certificates_call(rpc):
 
192
  """Executes the RPC call to get a list of public certificates.
 
193
 
 
194
  Args:
 
195
    rpc: a UserRPC instance.
 
196
 
 
197
  Returns:
 
198
    A list of PublicCertificate object.
 
199
  """
 
200
  request = app_identity_service_pb.GetPublicCertificateForAppRequest()
 
201
  response = app_identity_service_pb.GetPublicCertificateForAppResponse()
 
202
 
 
203
  def get_certs_result(rpc):
 
204
    """Check success, handle exceptions, and return converted RPC result.
 
205
 
 
206
    This method waits for the RPC if it has not yet finished, and calls the
 
207
    post-call hooks on the first invocation.
 
208
 
 
209
    Args:
 
210
      rpc: A UserRPC object.
 
211
 
 
212
    Returns:
 
213
      A list of PublicCertificate object.
 
214
    """
 
215
    assert rpc.service == _APP_IDENTITY_SERVICE_NAME, repr(rpc.service)
 
216
    assert rpc.method == _GET_CERTS_METHOD_NAME, repr(rpc.method)
 
217
    try:
 
218
      rpc.check_success()
 
219
    except apiproxy_errors.ApplicationError, err:
 
220
      raise _to_app_identity_error(err)
 
221
    result = []
 
222
    for cert in response.public_certificate_list_list():
 
223
      result.append(PublicCertificate(
 
224
          cert.key_name(), cert.x509_certificate_pem()))
 
225
    return result
 
226
 
 
227
 
 
228
  rpc.make_call(_GET_CERTS_METHOD_NAME, request, response, get_certs_result)
 
229
 
 
230
 
 
231
def make_get_service_account_name_call(rpc):
 
232
  """Get service account name of the app.
 
233
 
 
234
  Args:
 
235
    deadline: Optional deadline in seconds for the operation; the default
 
236
      is a system-specific deadline (typically 5 seconds).
 
237
 
 
238
  Returns:
 
239
    Service account name of the app.
 
240
  """
 
241
  request = app_identity_service_pb.GetServiceAccountNameRequest()
 
242
  response = app_identity_service_pb.GetServiceAccountNameResponse()
 
243
 
 
244
  if rpc.deadline is not None:
 
245
    request.set_deadline(rpc.deadline)
 
246
 
 
247
  def get_service_account_name_result(rpc):
 
248
    """Check success, handle exceptions, and return converted RPC result.
 
249
 
 
250
    This method waits for the RPC if it has not yet finished, and calls the
 
251
    post-call hooks on the first invocation.
 
252
 
 
253
    Args:
 
254
      rpc: A UserRPC object.
 
255
 
 
256
    Returns:
 
257
      A string which is service account name of the app.
 
258
    """
 
259
    assert rpc.service == _APP_IDENTITY_SERVICE_NAME, repr(rpc.service)
 
260
    assert rpc.method == _GET_SERVICE_ACCOUNT_NAME_METHOD_NAME, repr(rpc.method)
 
261
    try:
 
262
      rpc.check_success()
 
263
    except apiproxy_errors.ApplicationError, err:
 
264
      raise _to_app_identity_error(err)
 
265
 
 
266
    return response.service_account_name()
 
267
 
 
268
 
 
269
  rpc.make_call(_GET_SERVICE_ACCOUNT_NAME_METHOD_NAME, request,
 
270
                response, get_service_account_name_result)
 
271
 
 
272
 
 
273
def sign_blob(bytes_to_sign, deadline=None):
 
274
  """Signs a blob.
 
275
 
 
276
  Args:
 
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).
 
280
 
 
281
  Returns:
 
282
    Tuple, signing key name and signature.
 
283
  """
 
284
  rpc = create_rpc(deadline)
 
285
  make_sign_blob_call(rpc, bytes_to_sign)
 
286
  rpc.wait()
 
287
  return rpc.get_result()
 
288
 
 
289
 
 
290
def get_public_certificates(deadline=None):
 
291
  """Get public certificates.
 
292
 
 
293
  Args:
 
294
    deadline: Optional deadline in seconds for the operation; the default
 
295
      is a system-specific deadline (typically 5 seconds).
 
296
 
 
297
  Returns:
 
298
    A list of PublicCertificate object.
 
299
  """
 
300
  rpc = create_rpc(deadline)
 
301
  make_get_public_certificates_call(rpc)
 
302
  rpc.wait()
 
303
  return rpc.get_result()
 
304
 
 
305
 
 
306
def get_service_account_name(deadline=None):
 
307
  """Get service account name of the app.
 
308
 
 
309
  Args:
 
310
    deadline: Optional deadline in seconds for the operation; the default
 
311
      is a system-specific deadline (typically 5 seconds).
 
312
 
 
313
  Returns:
 
314
    Service account name of the app.
 
315
  """
 
316
  rpc = create_rpc(deadline)
 
317
  make_get_service_account_name_call(rpc)
 
318
  rpc.wait()
 
319
  return rpc.get_result()
 
320
 
 
321
 
 
322
def _ParseFullAppId(app_id):
 
323
  """Parse a full app id into partition, domain name and display app_id.
 
324
 
 
325
  Args:
 
326
    app_id: The full partitioned app id.
 
327
 
 
328
  Returns:
 
329
    A tuple (partition, domain_name, display_app_id).  The partition
 
330
    and domain name may be empty.
 
331
  """
 
332
  partition = ''
 
333
  psep = app_id.find(_PARTITION_SEPARATOR)
 
334
  if psep > 0:
 
335
    partition = app_id[:psep]
 
336
    app_id = app_id[psep+1:]
 
337
  domain_name = ''
 
338
  dsep = app_id.find(_DOMAIN_SEPARATOR)
 
339
  if dsep > 0:
 
340
    domain_name = app_id[:dsep]
 
341
    app_id = app_id[dsep+1:]
 
342
  return partition, domain_name, app_id
 
343
 
 
344
 
 
345
def get_application_id():
 
346
  """Get the application id of an app.
 
347
 
 
348
  Returns:
 
349
    The application id of the app.
 
350
  """
 
351
  full_app_id = os.getenv('APPLICATION_ID')
 
352
  _, domain_name, display_app_id = _ParseFullAppId(full_app_id)
 
353
  if domain_name:
 
354
    return '%s%s%s' % (domain_name, _DOMAIN_SEPARATOR, display_app_id)
 
355
  return display_app_id
 
356
 
 
357
 
 
358
def get_default_version_hostname():
 
359
  """Get the standard hostname of the default version of the app.
 
360
 
 
361
  For example if your application_id is my-app then the result might be
 
362
  my-app.appspot.com.
 
363
 
 
364
  Returns:
 
365
    The standard hostname of the default version of the application.
 
366
  """
 
367
 
 
368
 
 
369
 
 
370
 
 
371
 
 
372
  return os.getenv('DEFAULT_VERSION_HOSTNAME')
 
373
 
 
374
 
 
375
def make_get_access_token_call(rpc, scopes):
 
376
  """OAuth2 access token to act on behalf of the application (async, uncached).
 
377
 
 
378
  Most developers should use get_access_token instead.
 
379
 
 
380
  Args:
 
381
    rpc: RPC object.
 
382
    scopes: The requested API scope string, or a list of strings.
 
383
  Raises:
 
384
    InvalidScope: if the scopes are unspecified or invalid.
 
385
  """
 
386
 
 
387
  request = app_identity_service_pb.GetAccessTokenRequest()
 
388
  if not scopes:
 
389
    raise InvalidScope('No scopes specified.')
 
390
  if isinstance(scopes, basestring):
 
391
    request.add_scope(scopes)
 
392
  else:
 
393
    for scope in scopes:
 
394
      request.add_scope(scope)
 
395
  response = app_identity_service_pb.GetAccessTokenResponse()
 
396
 
 
397
  def get_access_token_result(rpc):
 
398
    """Check success, handle exceptions, and return converted RPC result.
 
399
 
 
400
    This method waits for the RPC if it has not yet finished, and calls the
 
401
    post-call hooks on the first invocation.
 
402
 
 
403
    Args:
 
404
      rpc: A UserRPC object.
 
405
 
 
406
    Returns:
 
407
      Pair, Access token (string) and expiration time (seconds since the epoch).
 
408
    """
 
409
    assert rpc.service == _APP_IDENTITY_SERVICE_NAME, repr(rpc.service)
 
410
    assert rpc.method == _GET_ACCESS_TOKEN_METHOD_NAME, repr(rpc.method)
 
411
    try:
 
412
      rpc.check_success()
 
413
    except apiproxy_errors.ApplicationError, err:
 
414
      raise _to_app_identity_error(err)
 
415
 
 
416
    return response.access_token(), response.expiration_time()
 
417
 
 
418
 
 
419
  rpc.make_call(_GET_ACCESS_TOKEN_METHOD_NAME, request,
 
420
                response, get_access_token_result)
 
421
 
 
422
 
 
423
def get_access_token_uncached(scopes, deadline=None):
 
424
  """OAuth2 access token to act on behalf of the application (sync, uncached).
 
425
 
 
426
  Most developers should use get_access_token instead.
 
427
 
 
428
  Args:
 
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).
 
432
  Returns:
 
433
    Pair, Access token (string) and expiration time (seconds since the epoch).
 
434
  """
 
435
  rpc = create_rpc(deadline)
 
436
  make_get_access_token_call(rpc, scopes)
 
437
  rpc.wait()
 
438
  return rpc.get_result()
 
439
 
 
440
 
 
441
def get_access_token(scopes):
 
442
  """OAuth2 access token to act on behalf of the application, cached.
 
443
 
 
444
  Generates and caches an OAuth2 access token for the service account for the
 
445
  appengine application.
 
446
 
 
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.
 
451
 
 
452
  Args:
 
453
    scopes: The requested API scope string, or a list of strings.
 
454
  Returns:
 
455
    Pair, Access token (string) and expiration time (seconds since the epoch).
 
456
  """
 
457
 
 
458
  memcache_key = _MEMCACHE_KEY_PREFIX + str(scopes)
 
459
  memcache_value = memcache.get(memcache_key, namespace=_MEMCACHE_NAMESPACE)
 
460
  if memcache_value:
 
461
    access_token, expires_at = memcache_value
 
462
  else:
 
463
    access_token, expires_at = get_access_token_uncached(scopes)
 
464
 
 
465
    memcache.add(memcache_key, (access_token, expires_at), expires_at - 300,
 
466
                 namespace=_MEMCACHE_NAMESPACE)
 
467
  return access_token, expires_at