Skip to content

Commit 95b81f0

Browse files
authored
Merge pull request #127 from Tanc009/master
push
2 parents 3752878 + c59311a commit 95b81f0

File tree

122 files changed

+4772
-82
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

122 files changed

+4772
-82
lines changed

jdcloud_sdk/core/const.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,12 @@
1515
# limitations under the License.
1616

1717
JDCLOUD2 = 'JDCLOUD2'
18+
JDCLOUD3 = 'JDCLOUD3'
1819
JDCLOUD_ALGORITHM = 'JDCLOUD2-HMAC-SHA256'
20+
JDCLOUD3_ALGORITHM = 'JDCLOUD3-HMAC-SHA256'
1921
JDCLOUD_REQUEST = 'jdcloud2_request'
22+
JDCLOUD3_REQUEST = 'jdcloud3_request'
23+
2024
JDCLOUD_DATE = 'x-jdcloud-date'
2125
JDCLOUD_SECURITY_TOKEN = 'x-jdcloud-security-token'
2226
JDCLOUD_CONTENT_SHA256 = 'x-jdcloud-content-sha256'

jdcloud_sdk/core/jdcloudclient.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ def __get_region_id(self, request):
107107
if hasattr(request.parameters, 'regionId') and request.parameters.regionId is not None:
108108
return request.parameters.regionId
109109

110-
return 'jdcloud-api' # when no region, use this value to fill field for sign
110+
return 'jdcloud-api'
111111

112112
def __process_response(self, method, response):
113113
jd_resp = JDCloudResponse()

jdcloud_sdk/core/jdcloudresponse.py

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -35,14 +35,13 @@ def load_hook(self, resp_dict):
3535
if request_id:
3636
self.request_id = request_id
3737

38-
error = resp_dict.get('error')
39-
if error:
40-
self.error = ErrorResponse(error.get('status'), error.get('code'), error.get('message'))
41-
42-
result = resp_dict.get('result')
43-
if result:
44-
self.result = resp_dict['result']
45-
return self
38+
error = resp_dict.get('error')
39+
if request_id and error:
40+
self.error = ErrorResponse(error.get('status'), error.get('code'), error.get('message'))
41+
42+
result = resp_dict.get('result')
43+
if request_id and result:
44+
self.result = resp_dict['result']
4645
return resp_dict
4746

4847

jdcloud_sdk/core/parameterbuilder.py

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -121,15 +121,7 @@ def build_body(self, request):
121121

122122
class ParametersEncoder(json.JSONEncoder):
123123
def default(self, o):
124-
tmp_dict = o.__dict__
125-
keywords = ['from', 'not', 'exec']
126-
for keyword in keywords:
127-
replacement = keyword + '_'
128-
if replacement in tmp_dict:
129-
tmp_dict[keyword] = tmp_dict[replacement]
130-
tmp_dict.pop(replacement)
131-
132-
return tmp_dict
124+
return o.__dict__
133125

134126

135127
def get_parameter_dict(parameters):

jdcloud_sdk/core/signer.py

Lines changed: 106 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,10 @@
2222
import uuid
2323
from . import const
2424
from .logger import INFO
25-
from .util import quote
25+
from .util import quote, unquote_plus, parse_url
2626

2727

2828
class Signer(object):
29-
3029
ignored_headers = ['authorization', 'user-agent']
3130

3231
def __init__(self, logger):
@@ -36,38 +35,43 @@ def sign(self, method, service, region, uri, headers, data, credential, security
3635
uri_dict = self.__url_path_to_dict(uri)
3736
host = uri_dict['host']
3837
port = uri_dict['port']
38+
path = uri_dict['path']
3939
query = uri_dict['query']
40-
canonical_uri = quote(uri_dict['path'])
4140

4241
if port and port not in ['80', '443']:
4342
full_host = host + ':' + port
4443
else:
4544
full_host = host
45+
return self.signV3(method, service, region, full_host, path, query, headers, data, credential, security_token)
4646

47+
def signV3(self, method, service, region, host, path, query, headers, data, credential, security_token):
48+
canonical_host = self.__build_canonical_host(host)
4749
now = self.__now()
48-
jdcloud_date = now.strftime('%Y%m%dT%H%M%SZ')
49-
datestamp = now.strftime('%Y%m%d') # Date w/o time, used in credential scope
5050
nonce = str(uuid.uuid4())
51-
headers[const.JDCLOUD_DATE] = jdcloud_date
52-
headers[const.JDCLOUD_NONCE] = nonce
51+
jdcloud_date = now.strftime('%Y%m%dT%H%M%SZ')
52+
if const.JDCLOUD_DATE in headers:
53+
jdcloud_date = headers[const.JDCLOUD_DATE]
54+
if const.JDCLOUD_NONCE in headers:
55+
nonce = headers[const.JDCLOUD_NONCE]
56+
date_str = jdcloud_date[:8]
5357

5458
canonical_querystring = self.__normalize_query_string(query)
55-
canonical_headers, signed_headers = self.__build_canonical_headers(headers, security_token, full_host)
59+
canonical_headers, signed_headers = self.__build_canonical_headers(headers, security_token, canonical_host)
5660

5761
payload_hash = self.__sha256_hash(data)
5862

5963
canonical_request = (method + '\n' +
60-
canonical_uri + '\n' +
64+
self.__build_canonical_uri(path) + '\n' +
6165
canonical_querystring + '\n' +
6266
canonical_headers + '\n' +
6367
signed_headers + '\n' +
6468
payload_hash)
6569

66-
algorithm = const.JDCLOUD_ALGORITHM
67-
credential_scope = (datestamp + '/' +
70+
algorithm = const.JDCLOUD3_ALGORITHM
71+
credential_scope = (date_str + '/' +
6872
region + '/' +
6973
service + '/' +
70-
const.JDCLOUD_REQUEST)
74+
const.JDCLOUD3_REQUEST)
7175
string_to_sign = (algorithm + '\n' +
7276
jdcloud_date + '\n' +
7377
credential_scope + '\n' +
@@ -76,35 +80,62 @@ def sign(self, method, service, region, uri, headers, data, credential, security
7680
self.__logger.log(INFO, '---canonical_request---\n' + canonical_request)
7781
self.__logger.log(INFO, '----string_to_sign---\n' + string_to_sign)
7882

79-
signing_key = self.__get_signature_key(credential.secret_key, datestamp, region, service)
83+
signing_key = self.__get_signature_key(credential.secret_key, date_str, region, service)
8084
encoded = string_to_sign.encode('utf-8')
8185
signature = hmac.new(signing_key, encoded, hashlib.sha256).hexdigest()
8286

8387
authorization_header = (
84-
algorithm + ' ' +
85-
'Credential=' + credential.access_key + '/' + credential_scope + ', ' +
86-
'SignedHeaders=' + signed_headers + ', ' +
87-
'Signature=' + signature
88+
algorithm + ' ' +
89+
'Credential=' + credential.access_key + '/' + credential_scope + ', ' +
90+
'SignedHeaders=' + signed_headers + ', ' +
91+
'Signature=' + signature
8892
)
8993

9094
headers.update({
9195
const.JDCLOUD_AUTH: authorization_header,
9296
const.JDCLOUD_DATE: jdcloud_date,
9397
const.JDCLOUD_CONTENT_SHA256: payload_hash,
94-
const.JDCLOUD_ALGORITHM: const.JDCLOUD_ALGORITHM,
98+
const.JDCLOUD3_ALGORITHM: const.JDCLOUD3_ALGORITHM,
9599
const.JDCLOUD_NONCE: nonce
96100
})
97101

98102
if security_token:
99103
headers.update({const.JDCLOUD_SECURITY_TOKEN: security_token})
100104

101-
def __normalize_query_string(self, query):
102-
params = (list(map(str.strip, s.split("=")))
103-
for s in query.split('&')
104-
if len(s) > 0)
105+
def __build_canonical_host(self, full_host):
106+
if full_host.lower().find('http://') == 0:
107+
full_host = full_host[7:]
108+
elif full_host.lower().find('https://') == 0:
109+
full_host = full_host[8:]
110+
return full_host
105111

106-
normalized = '&'.join('%s=%s' % (p[0], p[1] if len(p) == 2 else ('%3D'.join(p[1:]) if len(p) > 2 else ''))
107-
for p in sorted(params))
112+
def __normalize_query_string(self, query):
113+
params = []
114+
if isinstance(query, str):
115+
for s in query.split('&'):
116+
if len(s) <= 0:
117+
continue
118+
list = []
119+
for val in s.split('='):
120+
list.append(self.__urlencode(self.__urldecode(val)))
121+
params.append(list)
122+
elif isinstance(query, dict):
123+
for key in query.keys():
124+
list = []
125+
list.append(self.__urlencode(self.__urldecode(key)))
126+
list.append(self.__urlencode(self.__urldecode(query[key])))
127+
params.append(list)
128+
129+
normalized = ''
130+
for p in sorted(params):
131+
if p[0] == '':
132+
continue
133+
elif len(p) == 2:
134+
normalized += '%s=%s&' % (p[0], p[1])
135+
elif len(p) > 2:
136+
normalized += '%3D'.join(p[1:]) + '&'
137+
if normalized.endswith('&'):
138+
normalized = normalized[:normalized.__len__()-1]
108139
return normalized
109140

110141
def __now(self):
@@ -113,39 +144,58 @@ def __now(self):
113144
def __url_path_to_dict(self, path):
114145
"""http://stackoverflow.com/a/17892757/142207"""
115146

116-
pattern = (r'^'
117-
r'((?P<schema>.+?)://)?'
118-
r'((?P<user>.+?)(:(?P<password>.*?))?@)?'
119-
r'(?P<host>.*?)'
120-
r'(:(?P<port>\d+?))?'
121-
r'(?P<path>/.*?)?'
122-
r'(\?(?P<query>.*?))?'
123-
r'$')
124-
regex = re.compile(pattern)
125-
match = regex.match(path)
126-
group_dict = match.groupdict() if match is not None else None
127-
128-
if group_dict['path'] is None:
129-
group_dict['path'] = '/'
130-
131-
if group_dict['query'] is None:
132-
group_dict['query'] = ''
133-
134-
return group_dict
147+
# pattern = (r'^'
148+
# r'((?P<schema>.+?)://)?'
149+
# r'((?P<user>.+?)(:(?P<password>.*?))?@)?'
150+
# r'(?P<host>.*?)'
151+
# r'(:(?P<port>\d+?))?'
152+
# r'(?P<path>/.*?)?'
153+
# r'(\?(?P<query>.*?))?'
154+
# r'$')
155+
# regex = re.compile(pattern)
156+
# match = regex.match(path)
157+
# group_dict = match.groupdict() if match is not None else None
158+
#
159+
# if group_dict['path'] is None:
160+
# group_dict['path'] = '/'
161+
#
162+
# if group_dict['query'] is None:
163+
# group_dict['query'] = ''
164+
# return group_dict
165+
return parse_url(path)
135166

136167
def __sign(self, key, msg):
137168
return hmac.new(key, msg.encode('utf-8'), hashlib.sha256).digest()
138169

139170
def __get_signature_key(self, key, date_stamp, region_name, service_name):
140-
k_date = self.__sign((const.JDCLOUD2 + key).encode('utf-8'), date_stamp)
171+
k_date = self.__sign((const.JDCLOUD3 + key).encode('utf-8'), date_stamp)
141172
k_region = self.__sign(k_date, region_name)
142173
k_service = self.__sign(k_region, service_name)
143-
k_signing = self.__sign(k_service, const.JDCLOUD_REQUEST)
174+
k_signing = self.__sign(k_service, const.JDCLOUD3_REQUEST)
144175
return k_signing
145176

146177
def __sha256_hash(self, val):
147178
return hashlib.sha256(val.encode('utf-8')).hexdigest()
148179

180+
def __build_canonical_uri(self, path):
181+
reg = re.compile('/+')
182+
decoded_path = reg.sub('/', self.__urldecode(path))
183+
if decoded_path == '':
184+
return '/'
185+
encoded_path = self.__urlencode_ignore_slashes(decoded_path)
186+
if encoded_path.startswith('/'):
187+
return encoded_path
188+
return '/' + encoded_path
189+
190+
def __urlencode(self, value):
191+
return quote(value, '~')
192+
193+
def __urlencode_ignore_slashes(self, value):
194+
return quote(value, '/~')
195+
196+
def __urldecode(self, value):
197+
return unquote_plus(value)
198+
149199
def __build_canonical_headers(self, req_headers, security_token, full_host):
150200
headers = ['host'] # add host header first
151201
signed_values = {}
@@ -166,9 +216,18 @@ def __build_canonical_headers(self, req_headers, security_token, full_host):
166216
canonical_values = []
167217
for key in headers:
168218
if key == 'host':
169-
canonical_values.append('host:' + full_host)
219+
canonical_values.append('host:' + full_host.strip())
170220
else:
171-
canonical_values.append(key + ':' + signed_values[key])
221+
# canonical_values.append(key + ':' + )
222+
header_value = signed_values[key]
223+
if isinstance(header_value, str):
224+
canonical_values.append(key + ':' + header_value.strip())
225+
elif isinstance(header_value, list):
226+
arr = []
227+
for val in header_value:
228+
arr.append(val.strip())
229+
canonical_values.append(key + ':' + ','.join(arr))
230+
172231

173232
canonical_headers = '\n'.join(canonical_values) + '\n'
174233

0 commit comments

Comments
 (0)