gcloud 0.0.2 → 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data.tar.gz.sig +2 -3
- data/CHANGELOG +4 -0
- data/LICENSE +674 -0
- data/Manifest +111 -0
- data/README.md +4 -3
- data/bin/gcutil +53 -0
- data/gcloud.gemspec +4 -3
- data/packages/gcutil-1.7.1/CHANGELOG +197 -0
- data/packages/gcutil-1.7.1/LICENSE +202 -0
- data/packages/gcutil-1.7.1/VERSION +1 -0
- data/packages/gcutil-1.7.1/gcutil +53 -0
- data/packages/gcutil-1.7.1/lib/google_api_python_client/LICENSE +23 -0
- data/packages/gcutil-1.7.1/lib/google_api_python_client/apiclient/__init__.py +1 -0
- data/packages/gcutil-1.7.1/lib/google_api_python_client/apiclient/discovery.py +743 -0
- data/packages/gcutil-1.7.1/lib/google_api_python_client/apiclient/errors.py +123 -0
- data/packages/gcutil-1.7.1/lib/google_api_python_client/apiclient/ext/__init__.py +0 -0
- data/packages/gcutil-1.7.1/lib/google_api_python_client/apiclient/http.py +1443 -0
- data/packages/gcutil-1.7.1/lib/google_api_python_client/apiclient/mimeparse.py +172 -0
- data/packages/gcutil-1.7.1/lib/google_api_python_client/apiclient/model.py +385 -0
- data/packages/gcutil-1.7.1/lib/google_api_python_client/apiclient/schema.py +303 -0
- data/packages/gcutil-1.7.1/lib/google_api_python_client/oauth2client/__init__.py +1 -0
- data/packages/gcutil-1.7.1/lib/google_api_python_client/oauth2client/anyjson.py +32 -0
- data/packages/gcutil-1.7.1/lib/google_api_python_client/oauth2client/appengine.py +528 -0
- data/packages/gcutil-1.7.1/lib/google_api_python_client/oauth2client/client.py +1139 -0
- data/packages/gcutil-1.7.1/lib/google_api_python_client/oauth2client/clientsecrets.py +105 -0
- data/packages/gcutil-1.7.1/lib/google_api_python_client/oauth2client/crypt.py +244 -0
- data/packages/gcutil-1.7.1/lib/google_api_python_client/oauth2client/django_orm.py +124 -0
- data/packages/gcutil-1.7.1/lib/google_api_python_client/oauth2client/file.py +107 -0
- data/packages/gcutil-1.7.1/lib/google_api_python_client/oauth2client/locked_file.py +343 -0
- data/packages/gcutil-1.7.1/lib/google_api_python_client/oauth2client/multistore_file.py +379 -0
- data/packages/gcutil-1.7.1/lib/google_api_python_client/oauth2client/tools.py +174 -0
- data/packages/gcutil-1.7.1/lib/google_api_python_client/uritemplate/__init__.py +147 -0
- data/packages/gcutil-1.7.1/lib/google_apputils/LICENSE +202 -0
- data/packages/gcutil-1.7.1/lib/google_apputils/google/__init__.py +3 -0
- data/packages/gcutil-1.7.1/lib/google_apputils/google/apputils/__init__.py +3 -0
- data/packages/gcutil-1.7.1/lib/google_apputils/google/apputils/app.py +356 -0
- data/packages/gcutil-1.7.1/lib/google_apputils/google/apputils/appcommands.py +783 -0
- data/packages/gcutil-1.7.1/lib/google_apputils/google/apputils/basetest.py +1260 -0
- data/packages/gcutil-1.7.1/lib/google_apputils/google/apputils/datelib.py +421 -0
- data/packages/gcutil-1.7.1/lib/google_apputils/google/apputils/debug.py +60 -0
- data/packages/gcutil-1.7.1/lib/google_apputils/google/apputils/file_util.py +181 -0
- data/packages/gcutil-1.7.1/lib/google_apputils/google/apputils/resources.py +67 -0
- data/packages/gcutil-1.7.1/lib/google_apputils/google/apputils/run_script_module.py +217 -0
- data/packages/gcutil-1.7.1/lib/google_apputils/google/apputils/setup_command.py +159 -0
- data/packages/gcutil-1.7.1/lib/google_apputils/google/apputils/shellutil.py +49 -0
- data/packages/gcutil-1.7.1/lib/google_apputils/google/apputils/stopwatch.py +204 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/__init__.py +0 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/auth_helper.py +140 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/auth_helper_test.py +149 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/auto_auth.py +130 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/auto_auth_test.py +75 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/basic_cmds.py +128 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/basic_cmds_test.py +111 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/command_base.py +1808 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/command_base_test.py +1651 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/compute/v1beta13.json +2851 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/compute/v1beta14.json +3361 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/disk_cmds.py +342 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/disk_cmds_test.py +474 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/firewall_cmds.py +344 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/firewall_cmds_test.py +231 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/flags_cache.py +274 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/gcutil +89 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/gcutil_logging.py +69 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/image_cmds.py +262 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/image_cmds_test.py +172 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/instance_cmds.py +1506 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/instance_cmds_test.py +1904 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/kernel_cmds.py +91 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/kernel_cmds_test.py +56 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/machine_type_cmds.py +106 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/machine_type_cmds_test.py +59 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/metadata.py +96 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/metadata_lib.py +357 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/metadata_test.py +84 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/mock_api.py +420 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/mock_metadata.py +58 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/move_cmds.py +824 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/move_cmds_test.py +307 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/network_cmds.py +178 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/network_cmds_test.py +133 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/operation_cmds.py +181 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/operation_cmds_test.py +196 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/path_initializer.py +38 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/project_cmds.py +173 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/project_cmds_test.py +111 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/scopes.py +61 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/scopes_test.py +50 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/snapshot_cmds.py +276 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/snapshot_cmds_test.py +260 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/ssh_keys.py +266 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/ssh_keys_test.py +128 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/table_formatter.py +563 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/thread_pool.py +188 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/thread_pool_test.py +88 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/utils.py +208 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/utils_test.py +193 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/version.py +17 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/version_checker.py +246 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/version_checker_test.py +271 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/zone_cmds.py +151 -0
- data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/zone_cmds_test.py +60 -0
- data/packages/gcutil-1.7.1/lib/httplib2/LICENSE +21 -0
- data/packages/gcutil-1.7.1/lib/httplib2/httplib2/__init__.py +1630 -0
- data/packages/gcutil-1.7.1/lib/httplib2/httplib2/cacerts.txt +714 -0
- data/packages/gcutil-1.7.1/lib/httplib2/httplib2/iri2uri.py +110 -0
- data/packages/gcutil-1.7.1/lib/httplib2/httplib2/socks.py +438 -0
- data/packages/gcutil-1.7.1/lib/iso8601/LICENSE +20 -0
- data/packages/gcutil-1.7.1/lib/iso8601/iso8601/__init__.py +1 -0
- data/packages/gcutil-1.7.1/lib/iso8601/iso8601/iso8601.py +102 -0
- data/packages/gcutil-1.7.1/lib/iso8601/iso8601/test_iso8601.py +111 -0
- data/packages/gcutil-1.7.1/lib/python_gflags/AUTHORS +2 -0
- data/packages/gcutil-1.7.1/lib/python_gflags/LICENSE +28 -0
- data/packages/gcutil-1.7.1/lib/python_gflags/gflags.py +2862 -0
- data/packages/gcutil-1.7.1/lib/python_gflags/gflags2man.py +544 -0
- data/packages/gcutil-1.7.1/lib/python_gflags/gflags_validators.py +187 -0
- metadata +118 -5
- metadata.gz.sig +0 -0
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# Copyright (C) 2011 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
|
+
"""Utilities for reading OAuth 2.0 client secret files.
|
|
16
|
+
|
|
17
|
+
A client_secrets.json file contains all the information needed to interact with
|
|
18
|
+
an OAuth 2.0 protected service.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
from anyjson import simplejson
|
|
25
|
+
|
|
26
|
+
# Properties that make a client_secrets.json file valid.
|
|
27
|
+
TYPE_WEB = 'web'
|
|
28
|
+
TYPE_INSTALLED = 'installed'
|
|
29
|
+
|
|
30
|
+
VALID_CLIENT = {
|
|
31
|
+
TYPE_WEB: {
|
|
32
|
+
'required': [
|
|
33
|
+
'client_id',
|
|
34
|
+
'client_secret',
|
|
35
|
+
'redirect_uris',
|
|
36
|
+
'auth_uri',
|
|
37
|
+
'token_uri'],
|
|
38
|
+
'string': [
|
|
39
|
+
'client_id',
|
|
40
|
+
'client_secret'
|
|
41
|
+
]
|
|
42
|
+
},
|
|
43
|
+
TYPE_INSTALLED: {
|
|
44
|
+
'required': [
|
|
45
|
+
'client_id',
|
|
46
|
+
'client_secret',
|
|
47
|
+
'redirect_uris',
|
|
48
|
+
'auth_uri',
|
|
49
|
+
'token_uri'],
|
|
50
|
+
'string': [
|
|
51
|
+
'client_id',
|
|
52
|
+
'client_secret'
|
|
53
|
+
]
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
class Error(Exception):
|
|
58
|
+
"""Base error for this module."""
|
|
59
|
+
pass
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class InvalidClientSecretsError(Error):
|
|
63
|
+
"""Format of ClientSecrets file is invalid."""
|
|
64
|
+
pass
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _validate_clientsecrets(obj):
|
|
68
|
+
if obj is None or len(obj) != 1:
|
|
69
|
+
raise InvalidClientSecretsError('Invalid file format.')
|
|
70
|
+
client_type = obj.keys()[0]
|
|
71
|
+
if client_type not in VALID_CLIENT.keys():
|
|
72
|
+
raise InvalidClientSecretsError('Unknown client type: %s.' % client_type)
|
|
73
|
+
client_info = obj[client_type]
|
|
74
|
+
for prop_name in VALID_CLIENT[client_type]['required']:
|
|
75
|
+
if prop_name not in client_info:
|
|
76
|
+
raise InvalidClientSecretsError(
|
|
77
|
+
'Missing property "%s" in a client type of "%s".' % (prop_name,
|
|
78
|
+
client_type))
|
|
79
|
+
for prop_name in VALID_CLIENT[client_type]['string']:
|
|
80
|
+
if client_info[prop_name].startswith('[['):
|
|
81
|
+
raise InvalidClientSecretsError(
|
|
82
|
+
'Property "%s" is not configured.' % prop_name)
|
|
83
|
+
return client_type, client_info
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def load(fp):
|
|
87
|
+
obj = simplejson.load(fp)
|
|
88
|
+
return _validate_clientsecrets(obj)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def loads(s):
|
|
92
|
+
obj = simplejson.loads(s)
|
|
93
|
+
return _validate_clientsecrets(obj)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def loadfile(filename):
|
|
97
|
+
try:
|
|
98
|
+
fp = file(filename, 'r')
|
|
99
|
+
try:
|
|
100
|
+
obj = simplejson.load(fp)
|
|
101
|
+
finally:
|
|
102
|
+
fp.close()
|
|
103
|
+
except IOError:
|
|
104
|
+
raise InvalidClientSecretsError('File not found: "%s"' % filename)
|
|
105
|
+
return _validate_clientsecrets(obj)
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
#!/usr/bin/python2.4
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
#
|
|
4
|
+
# Copyright (C) 2011 Google Inc.
|
|
5
|
+
#
|
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
# you may not use this file except in compliance with the License.
|
|
8
|
+
# You may obtain a copy of the License at
|
|
9
|
+
#
|
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
#
|
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
# See the License for the specific language governing permissions and
|
|
16
|
+
# limitations under the License.
|
|
17
|
+
|
|
18
|
+
import base64
|
|
19
|
+
import hashlib
|
|
20
|
+
import logging
|
|
21
|
+
import time
|
|
22
|
+
|
|
23
|
+
from OpenSSL import crypto
|
|
24
|
+
from anyjson import simplejson
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
CLOCK_SKEW_SECS = 300 # 5 minutes in seconds
|
|
28
|
+
AUTH_TOKEN_LIFETIME_SECS = 300 # 5 minutes in seconds
|
|
29
|
+
MAX_TOKEN_LIFETIME_SECS = 86400 # 1 day in seconds
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class AppIdentityError(Exception):
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class Verifier(object):
|
|
37
|
+
"""Verifies the signature on a message."""
|
|
38
|
+
|
|
39
|
+
def __init__(self, pubkey):
|
|
40
|
+
"""Constructor.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
pubkey, OpenSSL.crypto.PKey, The public key to verify with.
|
|
44
|
+
"""
|
|
45
|
+
self._pubkey = pubkey
|
|
46
|
+
|
|
47
|
+
def verify(self, message, signature):
|
|
48
|
+
"""Verifies a message against a signature.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
message: string, The message to verify.
|
|
52
|
+
signature: string, The signature on the message.
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
True if message was singed by the private key associated with the public
|
|
56
|
+
key that this object was constructed with.
|
|
57
|
+
"""
|
|
58
|
+
try:
|
|
59
|
+
crypto.verify(self._pubkey, signature, message, 'sha256')
|
|
60
|
+
return True
|
|
61
|
+
except:
|
|
62
|
+
return False
|
|
63
|
+
|
|
64
|
+
@staticmethod
|
|
65
|
+
def from_string(key_pem, is_x509_cert):
|
|
66
|
+
"""Construct a Verified instance from a string.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
key_pem: string, public key in PEM format.
|
|
70
|
+
is_x509_cert: bool, True if key_pem is an X509 cert, otherwise it is
|
|
71
|
+
expected to be an RSA key in PEM format.
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
Verifier instance.
|
|
75
|
+
|
|
76
|
+
Raises:
|
|
77
|
+
OpenSSL.crypto.Error if the key_pem can't be parsed.
|
|
78
|
+
"""
|
|
79
|
+
if is_x509_cert:
|
|
80
|
+
pubkey = crypto.load_certificate(crypto.FILETYPE_PEM, key_pem)
|
|
81
|
+
else:
|
|
82
|
+
pubkey = crypto.load_privatekey(crypto.FILETYPE_PEM, key_pem)
|
|
83
|
+
return Verifier(pubkey)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class Signer(object):
|
|
87
|
+
"""Signs messages with a private key."""
|
|
88
|
+
|
|
89
|
+
def __init__(self, pkey):
|
|
90
|
+
"""Constructor.
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
pkey, OpenSSL.crypto.PKey, The private key to sign with.
|
|
94
|
+
"""
|
|
95
|
+
self._key = pkey
|
|
96
|
+
|
|
97
|
+
def sign(self, message):
|
|
98
|
+
"""Signs a message.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
message: string, Message to be signed.
|
|
102
|
+
|
|
103
|
+
Returns:
|
|
104
|
+
string, The signature of the message for the given key.
|
|
105
|
+
"""
|
|
106
|
+
return crypto.sign(self._key, message, 'sha256')
|
|
107
|
+
|
|
108
|
+
@staticmethod
|
|
109
|
+
def from_string(key, password='notasecret'):
|
|
110
|
+
"""Construct a Signer instance from a string.
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
key: string, private key in P12 format.
|
|
114
|
+
password: string, password for the private key file.
|
|
115
|
+
|
|
116
|
+
Returns:
|
|
117
|
+
Signer instance.
|
|
118
|
+
|
|
119
|
+
Raises:
|
|
120
|
+
OpenSSL.crypto.Error if the key can't be parsed.
|
|
121
|
+
"""
|
|
122
|
+
pkey = crypto.load_pkcs12(key, password).get_privatekey()
|
|
123
|
+
return Signer(pkey)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def _urlsafe_b64encode(raw_bytes):
|
|
127
|
+
return base64.urlsafe_b64encode(raw_bytes).rstrip('=')
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def _urlsafe_b64decode(b64string):
|
|
131
|
+
# Guard against unicode strings, which base64 can't handle.
|
|
132
|
+
b64string = b64string.encode('ascii')
|
|
133
|
+
padded = b64string + '=' * (4 - len(b64string) % 4)
|
|
134
|
+
return base64.urlsafe_b64decode(padded)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def _json_encode(data):
|
|
138
|
+
return simplejson.dumps(data, separators = (',', ':'))
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def make_signed_jwt(signer, payload):
|
|
142
|
+
"""Make a signed JWT.
|
|
143
|
+
|
|
144
|
+
See http://self-issued.info/docs/draft-jones-json-web-token.html.
|
|
145
|
+
|
|
146
|
+
Args:
|
|
147
|
+
signer: crypt.Signer, Cryptographic signer.
|
|
148
|
+
payload: dict, Dictionary of data to convert to JSON and then sign.
|
|
149
|
+
|
|
150
|
+
Returns:
|
|
151
|
+
string, The JWT for the payload.
|
|
152
|
+
"""
|
|
153
|
+
header = {'typ': 'JWT', 'alg': 'RS256'}
|
|
154
|
+
|
|
155
|
+
segments = [
|
|
156
|
+
_urlsafe_b64encode(_json_encode(header)),
|
|
157
|
+
_urlsafe_b64encode(_json_encode(payload)),
|
|
158
|
+
]
|
|
159
|
+
signing_input = '.'.join(segments)
|
|
160
|
+
|
|
161
|
+
signature = signer.sign(signing_input)
|
|
162
|
+
segments.append(_urlsafe_b64encode(signature))
|
|
163
|
+
|
|
164
|
+
logging.debug(str(segments))
|
|
165
|
+
|
|
166
|
+
return '.'.join(segments)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def verify_signed_jwt_with_certs(jwt, certs, audience):
|
|
170
|
+
"""Verify a JWT against public certs.
|
|
171
|
+
|
|
172
|
+
See http://self-issued.info/docs/draft-jones-json-web-token.html.
|
|
173
|
+
|
|
174
|
+
Args:
|
|
175
|
+
jwt: string, A JWT.
|
|
176
|
+
certs: dict, Dictionary where values of public keys in PEM format.
|
|
177
|
+
audience: string, The audience, 'aud', that this JWT should contain. If
|
|
178
|
+
None then the JWT's 'aud' parameter is not verified.
|
|
179
|
+
|
|
180
|
+
Returns:
|
|
181
|
+
dict, The deserialized JSON payload in the JWT.
|
|
182
|
+
|
|
183
|
+
Raises:
|
|
184
|
+
AppIdentityError if any checks are failed.
|
|
185
|
+
"""
|
|
186
|
+
segments = jwt.split('.')
|
|
187
|
+
|
|
188
|
+
if (len(segments) != 3):
|
|
189
|
+
raise AppIdentityError(
|
|
190
|
+
'Wrong number of segments in token: %s' % jwt)
|
|
191
|
+
signed = '%s.%s' % (segments[0], segments[1])
|
|
192
|
+
|
|
193
|
+
signature = _urlsafe_b64decode(segments[2])
|
|
194
|
+
|
|
195
|
+
# Parse token.
|
|
196
|
+
json_body = _urlsafe_b64decode(segments[1])
|
|
197
|
+
try:
|
|
198
|
+
parsed = simplejson.loads(json_body)
|
|
199
|
+
except:
|
|
200
|
+
raise AppIdentityError('Can\'t parse token: %s' % json_body)
|
|
201
|
+
|
|
202
|
+
# Check signature.
|
|
203
|
+
verified = False
|
|
204
|
+
for (keyname, pem) in certs.items():
|
|
205
|
+
verifier = Verifier.from_string(pem, True)
|
|
206
|
+
if (verifier.verify(signed, signature)):
|
|
207
|
+
verified = True
|
|
208
|
+
break
|
|
209
|
+
if not verified:
|
|
210
|
+
raise AppIdentityError('Invalid token signature: %s' % jwt)
|
|
211
|
+
|
|
212
|
+
# Check creation timestamp.
|
|
213
|
+
iat = parsed.get('iat')
|
|
214
|
+
if iat is None:
|
|
215
|
+
raise AppIdentityError('No iat field in token: %s' % json_body)
|
|
216
|
+
earliest = iat - CLOCK_SKEW_SECS
|
|
217
|
+
|
|
218
|
+
# Check expiration timestamp.
|
|
219
|
+
now = long(time.time())
|
|
220
|
+
exp = parsed.get('exp')
|
|
221
|
+
if exp is None:
|
|
222
|
+
raise AppIdentityError('No exp field in token: %s' % json_body)
|
|
223
|
+
if exp >= now + MAX_TOKEN_LIFETIME_SECS:
|
|
224
|
+
raise AppIdentityError(
|
|
225
|
+
'exp field too far in future: %s' % json_body)
|
|
226
|
+
latest = exp + CLOCK_SKEW_SECS
|
|
227
|
+
|
|
228
|
+
if now < earliest:
|
|
229
|
+
raise AppIdentityError('Token used too early, %d < %d: %s' %
|
|
230
|
+
(now, earliest, json_body))
|
|
231
|
+
if now > latest:
|
|
232
|
+
raise AppIdentityError('Token used too late, %d > %d: %s' %
|
|
233
|
+
(now, latest, json_body))
|
|
234
|
+
|
|
235
|
+
# Check audience.
|
|
236
|
+
if audience is not None:
|
|
237
|
+
aud = parsed.get('aud')
|
|
238
|
+
if aud is None:
|
|
239
|
+
raise AppIdentityError('No aud field in token: %s' % json_body)
|
|
240
|
+
if aud != audience:
|
|
241
|
+
raise AppIdentityError('Wrong recipient, %s != %s: %s' %
|
|
242
|
+
(aud, audience, json_body))
|
|
243
|
+
|
|
244
|
+
return parsed
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# Copyright (C) 2010 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
|
+
"""OAuth 2.0 utilities for Django.
|
|
16
|
+
|
|
17
|
+
Utilities for using OAuth 2.0 in conjunction with
|
|
18
|
+
the Django datastore.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
import oauth2client
|
|
24
|
+
import base64
|
|
25
|
+
import pickle
|
|
26
|
+
|
|
27
|
+
from django.db import models
|
|
28
|
+
from oauth2client.client import Storage as BaseStorage
|
|
29
|
+
|
|
30
|
+
class CredentialsField(models.Field):
|
|
31
|
+
|
|
32
|
+
__metaclass__ = models.SubfieldBase
|
|
33
|
+
|
|
34
|
+
def get_internal_type(self):
|
|
35
|
+
return "TextField"
|
|
36
|
+
|
|
37
|
+
def to_python(self, value):
|
|
38
|
+
if value is None:
|
|
39
|
+
return None
|
|
40
|
+
if isinstance(value, oauth2client.client.Credentials):
|
|
41
|
+
return value
|
|
42
|
+
return pickle.loads(base64.b64decode(value))
|
|
43
|
+
|
|
44
|
+
def get_db_prep_value(self, value, connection, prepared=False):
|
|
45
|
+
if value is None:
|
|
46
|
+
return None
|
|
47
|
+
return base64.b64encode(pickle.dumps(value))
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class FlowField(models.Field):
|
|
51
|
+
|
|
52
|
+
__metaclass__ = models.SubfieldBase
|
|
53
|
+
|
|
54
|
+
def get_internal_type(self):
|
|
55
|
+
return "TextField"
|
|
56
|
+
|
|
57
|
+
def to_python(self, value):
|
|
58
|
+
if value is None:
|
|
59
|
+
return None
|
|
60
|
+
if isinstance(value, oauth2client.client.Flow):
|
|
61
|
+
return value
|
|
62
|
+
return pickle.loads(base64.b64decode(value))
|
|
63
|
+
|
|
64
|
+
def get_db_prep_value(self, value, connection, prepared=False):
|
|
65
|
+
if value is None:
|
|
66
|
+
return None
|
|
67
|
+
return base64.b64encode(pickle.dumps(value))
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class Storage(BaseStorage):
|
|
71
|
+
"""Store and retrieve a single credential to and from
|
|
72
|
+
the datastore.
|
|
73
|
+
|
|
74
|
+
This Storage helper presumes the Credentials
|
|
75
|
+
have been stored as a CredenialsField
|
|
76
|
+
on a db model class.
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
def __init__(self, model_class, key_name, key_value, property_name):
|
|
80
|
+
"""Constructor for Storage.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
model: db.Model, model class
|
|
84
|
+
key_name: string, key name for the entity that has the credentials
|
|
85
|
+
key_value: string, key value for the entity that has the credentials
|
|
86
|
+
property_name: string, name of the property that is an CredentialsProperty
|
|
87
|
+
"""
|
|
88
|
+
self.model_class = model_class
|
|
89
|
+
self.key_name = key_name
|
|
90
|
+
self.key_value = key_value
|
|
91
|
+
self.property_name = property_name
|
|
92
|
+
|
|
93
|
+
def locked_get(self):
|
|
94
|
+
"""Retrieve Credential from datastore.
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
oauth2client.Credentials
|
|
98
|
+
"""
|
|
99
|
+
credential = None
|
|
100
|
+
|
|
101
|
+
query = {self.key_name: self.key_value}
|
|
102
|
+
entities = self.model_class.objects.filter(**query)
|
|
103
|
+
if len(entities) > 0:
|
|
104
|
+
credential = getattr(entities[0], self.property_name)
|
|
105
|
+
if credential and hasattr(credential, 'set_store'):
|
|
106
|
+
credential.set_store(self)
|
|
107
|
+
return credential
|
|
108
|
+
|
|
109
|
+
def locked_put(self, credentials):
|
|
110
|
+
"""Write a Credentials to the datastore.
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
credentials: Credentials, the credentials to store.
|
|
114
|
+
"""
|
|
115
|
+
args = {self.key_name: self.key_value}
|
|
116
|
+
entity = self.model_class(**args)
|
|
117
|
+
setattr(entity, self.property_name, credentials)
|
|
118
|
+
entity.save()
|
|
119
|
+
|
|
120
|
+
def locked_delete(self):
|
|
121
|
+
"""Delete Credentials from the datastore."""
|
|
122
|
+
|
|
123
|
+
query = {self.key_name: self.key_value}
|
|
124
|
+
entities = self.model_class.objects.filter(**query).delete()
|