Skip to content

Commit c0f91ad

Browse files
author
chenjing.cjcj
committed
v2.6.8
1 parent ce2760e commit c0f91ad

File tree

12 files changed

+381
-540
lines changed

12 files changed

+381
-540
lines changed

CHANGELOG.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
TOS SDK for Python 版本记录
22
===========================
3+
Version 2.6.8
4+
-------------
5+
- 增加:BucketTagging相关接口
6+
- 改变:重试时重新生成签名
7+
- 改变:签名只签必要的Header
8+
39
Version 2.6.7
410
-------------
511
- 修复:pre_signed_post_signature部分参数为可选

LICENSE

Lines changed: 200 additions & 494 deletions
Large diffs are not rendered by default.

tests/test_auth.py

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ def test_generate_presigned_url(self):
1919
with mock.patch('datetime.datetime', new=datetime_mock):
2020
tos_cli = tos.TosClient(tos.Auth('ak', 'sk', 'beijing'), 'tos-cn-beijing.volces.com')
2121
url = tos_cli.generate_presigned_url(Method='GET', Bucket='bkt', Key='key', ExpiresIn=86400)
22-
print(url)
2322
self.assertEqual(url,
2423
'https://bkt.tos-cn-beijing.volces.com/key?X-Tos-Algorithm=TOS4-HMAC-SHA256&X-Tos-Creden'
2524
'tial=ak%2F20210101%2Fbeijing%2Ftos%2Frequest&X-Tos-Date=20210101T000000Z&X-Tos-Expires=8'
@@ -31,14 +30,14 @@ def test_generate_presigned_url(self):
3130
self.assertEqual(url,
3231
'https://bkt.tos-cn-beijing.volces.com/key?X-Tos-Algorithm=TOS4-HMAC-SHA256&X-Tos-Credentia'
3332
'l=ak%2F20210101%2Fbeijing%2Ftos%2Frequest&X-Tos-Date=20210101T000000Z&X-Tos-Expires=36'
34-
'00&X-Tos-SignedHeaders=host&X-Tos-Security-Token=sts&X-Tos-Signature=3041fb481e31ec25f'
33+
'00&X-Tos-Security-Token=sts&X-Tos-SignedHeaders=host&X-Tos-Signature=3041fb481e31ec25f'
3534
'e7a1be44cafe25caf1cd97228710ee440b2ca1bd49bc563')
3635

3736
url = tos_cli.generate_presigned_url(Method='PUT', Bucket='bkt', Key='key', Params={'acl': ''})
3837
self.assertEqual(url,
3938
'https://bkt.tos-cn-beijing.volces.com/key?acl&X-Tos-Algorithm=TOS4-HMAC-SHA256&X-Tos-C'
4039
'redential=ak%2F20210101%2Fbeijing%2Ftos%2Frequest&X-Tos-Date=20210101T000000Z&X-Tos-Exp'
41-
'ires=3600&X-Tos-SignedHeaders=host&X-Tos-Security-Token=sts&X-Tos-Signature=51c89070206'
40+
'ires=3600&X-Tos-Security-Token=sts&X-Tos-SignedHeaders=host&X-Tos-Signature=51c89070206'
4241
'dd438fd8be1ed201a77335af80e33cad5fd408841590b99aa93d3')
4342

4443
def test_client2_presigned_url(self):
@@ -70,20 +69,19 @@ def test_client2_presigned_url(self):
7069
self.assertEqual(url.signed_url,
7170
'https://bkt.tos-cn-beijing.volces.com/key?acl&X-Tos-Algorithm=TOS4-HMAC-SHA256&X-Tos-C'
7271
'redential=ak%2F20210101%2Fbeijing%2Ftos%2Frequest&X-Tos-Date=20210101T000000Z&X-Tos-Exp'
73-
'ires=3600&X-Tos-SignedHeaders=host&X-Tos-Security-Token=sts&X-Tos-Signature=51c89070206'
72+
'ires=3600&X-Tos-Security-Token=sts&X-Tos-SignedHeaders=host&X-Tos-Signature=51c89070206'
7473
'dd438fd8be1ed201a77335af80e33cad5fd408841590b99aa93d3')
7574

7675
url = tos_cli.pre_signed_url(http_method=HttpMethodType.Http_Method_Put, bucket='bkt', key='key')
7776
self.assertEqual(url.signed_url,
7877
'https://bkt.tos-cn-beijing.volces.com/key?X-Tos-Algorithm=TOS4-HMAC-SHA256&X-Tos-Credentia'
7978
'l=ak%2F20210101%2Fbeijing%2Ftos%2Frequest&X-Tos-Date=20210101T000000Z&X-Tos-Expires=36'
80-
'00&X-Tos-SignedHeaders=host&X-Tos-Security-Token=sts&X-Tos-Signature=3041fb481e31ec25f'
79+
'00&X-Tos-Security-Token=sts&X-Tos-SignedHeaders=host&X-Tos-Signature=3041fb481e31ec25f'
8180
'e7a1be44cafe25caf1cd97228710ee440b2ca1bd49bc563')
8281
url = tos_cli.pre_signed_url(http_method=HttpMethodType.Http_Method_Put, bucket='bkt', key='key',
8382
expires=1234, header={'contentLength': 1000})
8483
self.assertTrue('1234' in url.signed_url)
85-
self.assertTrue('contentlength' in url.signed_url)
86-
url.signed_header['contentLength'] = 1000
84+
self.assertTrue('contentlength' not in url.signed_url)
8785

8886
self.anonymousCli = tos.TosClientV2(ak='', sk='', endpoint='tos-cn-beijing.volces.com', region='beijing')
8987
url = self.anonymousCli.pre_signed_url(http_method=HttpMethodType.Http_Method_Put, bucket='bkt', key='key',

tests/test_base_func.py

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
# -*- coding: utf-8 -*-
2-
import http
2+
import datetime
33
import os
44
import time
55
import unittest
66
from io import StringIO
7-
import http.client as httplib
7+
from unittest import mock
88
import requests
99
from requests.exceptions import RetryError
1010
from urllib3.exceptions import NewConnectionError
@@ -15,11 +15,13 @@
1515
convert_az_redundancy_type, PermissionType, convert_permission_type, GranteeType, convert_grantee_type, \
1616
convert_canned_type, CannedType, RedirectType, convert_redirect_type, StatusType, convert_status_type, \
1717
StorageClassInheritDirectiveType, convert_storage_class_inherit_directive_type, VersioningStatusType, \
18-
convert_versioning_status_type, ProtocolType, convert_protocol_type, CertStatus, convert_cert_status
18+
convert_versioning_status_type, ProtocolType, convert_protocol_type, CertStatus, convert_cert_status, \
19+
StaticCredentialsProvider
20+
from tos.auth import CredentialProviderAuth
1921
from tos.checkpoint import CancelHook
20-
from tos.clientv2 import _handler_retry_policy, _is_wrapper_data
22+
from tos.clientv2 import _handler_retry_policy, _is_wrapper_data, _signed_req
2123
from tos.exceptions import TosServerError, CancelNotWithAbortError, CancelWithAbortError
22-
from tos.http import Response
24+
from tos.http import Response, Request
2325
from tos.utils import SizeAdapter
2426

2527

@@ -226,6 +228,27 @@ def test_convert_enum_type(self):
226228
assert t == convert_cert_status(t.value)
227229
assert CertStatus.Cert_Unknown == convert_cert_status('test')
228230

231+
def test_sign_req(self):
232+
auth = CredentialProviderAuth(StaticCredentialsProvider('ak', 'sk', 'sts'), 'region')
233+
host = 'zzz.com'
234+
headers = {'content-type': 'application/json', 'Host': host}
235+
params = {'versionId': 'test'}
236+
237+
req = Request('GET', 'http://zzz.com/key', 'key', 'zzz.com',
238+
params=params,
239+
headers=headers)
240+
241+
datetime_mock = mock.Mock(wraps=datetime.datetime)
242+
datetime_mock.utcnow.return_value = datetime.datetime(2021, 1, 1)
243+
with mock.patch('datetime.datetime', new=datetime_mock):
244+
req = _signed_req(auth, req, host)
245+
self.assertEqual(req.headers['Authorization'],
246+
'TOS4-HMAC-SHA256 Credential=ak/20210101/region/tos/request, '
247+
'SignedHeaders=content-type;host;x-tos-date;x-tos-security-token, Signature=ca8eb8987663e61e740bc5be9078d6d4f716990573a56c86a6602d42faf3af7e')
248+
self.assertEqual(req.headers['Host'], host)
249+
self.assertEqual(req.headers['x-tos-date'], '20210101T000000Z')
250+
self.assertEqual(req.headers['x-tos-security-token'], 'sts')
251+
229252

230253
class args(object):
231254
def __init__(self, body, method, fun_name, client_exp, server_exp, want, expect_data=None):

tests/test_v2_bucket.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -717,6 +717,30 @@ def test_put_bucket_real_time_log(self):
717717
with self.assertRaises(TosServerError):
718718
self.client2.get_bucket_real_time_log(bucket_name)
719719

720+
def test_bucket_tagging(self):
721+
bucket_name = self.bucket_name # + '-bucket-tagging'
722+
self.client.create_bucket(bucket_name)
723+
self.bucket_delete.append(bucket_name)
724+
tag_set = [Tag(
725+
key='1',
726+
value='2'
727+
), Tag(
728+
key='3',
729+
value='4'
730+
)]
731+
self.client.put_bucket_tagging(bucket_name, tag_set)
732+
out = self.client.get_bucket_tagging(bucket_name)
733+
self.assertIsNotNone(out.request_id)
734+
self.assertEqual(len(out.tag_set), 2)
735+
self.assertEqual(out.tag_set[0].key, tag_set[0].key)
736+
self.assertEqual(out.tag_set[0].value, tag_set[0].value)
737+
self.assertEqual(out.tag_set[1].key, tag_set[1].key)
738+
self.assertEqual(out.tag_set[1].value, tag_set[1].value)
739+
delete_out = self.client.delete_bucket_tagging(bucket_name)
740+
self.assertIsNotNone(delete_out.request_id)
741+
with self.assertRaises(TosServerError):
742+
self.client.get_bucket_tagging(bucket_name)
743+
720744
def retry_assert(self, func):
721745
for i in range(5):
722746
if func():

tests/test_v2_object.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,8 +179,12 @@ def test_put_with_empty_content(self):
179179
content = content_io
180180
content.read()
181181
key = self.random_key()
182-
self.client.put_object(bucket_name, key)
183182
conn.close()
183+
file_name = self.random_filename()
184+
file = open(file_name, 'w')
185+
file.close()
186+
self.client.put_object_from_file(bucket_name, key, file_path=file_name)
187+
os.remove(file_name)
184188

185189
def test_put_with_illegal_name(self):
186190
bucket_name = self.bucket_name + '-put-object-with-illegal-name'

tos/__version__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = 'v2.6.7'
1+
__version__ = 'v2.6.8'

tos/auth.py

Lines changed: 32 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
import pytz
1111

12-
from .consts import DATE_FORMAT, UNSIGNED_PAYLOAD, LAST_MODIFY_TIME_DATE_FORMAT
12+
from .consts import DATE_FORMAT, UNSIGNED_PAYLOAD, LAST_MODIFY_TIME_DATE_FORMAT, SIGNATURE_QUERY_LOWER, V4_PREFIX
1313
from .credential import FederationCredentials, StaticCredentialsProvider
1414
from .exceptions import TosClientError
1515
from .models2 import PreSignedPostSignatureOutPut, ContentLengthRange
@@ -21,37 +21,47 @@
2121
def _canonical_query_string_params(params):
2222
results = []
2323
for param in sorted(params):
24+
if param.lower() == SIGNATURE_QUERY_LOWER:
25+
continue
2426
value = str(params[param])
2527
results.append('%s=%s' % (quote(param, safe='-_.~'),
2628
quote(value, safe='-_.~')))
2729
cqs = '&'.join(results)
2830
return cqs
2931

3032

31-
def _signed_headers(headers):
32-
hl = sorted(headers.items(), key=lambda d: d[0].lower())
33-
vl = []
34-
for v in hl:
35-
vl.append(v[0].lower())
36-
return ';'.join(vl)
33+
def _need_signed_headers(key, is_signing_query):
34+
return (key == "content-type" and not is_signing_query) or key.startswith(V4_PREFIX) or key == 'host'
35+
36+
37+
def _get_signed_headers(headers, is_signing_query=False):
38+
signed_headers = {}
39+
for k, v in headers.items():
40+
k = k.lower()
41+
if _need_signed_headers(k, is_signing_query):
42+
signed_headers[k] = v
43+
return sorted(signed_headers.items(), key=lambda d: d[0].lower())
44+
45+
46+
def _get_signed_header_key(signed_headers):
47+
return ';'.join([v[0] for v in signed_headers])
3748

3849

3950
def _canonical_headers(headers):
40-
hl = sorted(headers.items(), key=lambda d: d[0].lower())
4151
s = ''
42-
for val in hl:
52+
for val in headers:
4353
if isinstance(val[1], list):
4454
tlist = sorted(val[1])
4555
for v in tlist:
46-
s += val[0] + ':' + v + '\n'
56+
s += val[0].lower() + ':' + v + '\n'
4757
else:
4858
s += val[0].lower() + ':' + str(val[1]) + '\n'
4959
return s
5060

5161

52-
def _canonical_request(req):
62+
def _canonical_request(req, signed_headers):
5363
cr = [req.method.upper(), quote(req.path, safe='/~'), _canonical_query_string_params(req.params),
54-
_canonical_headers(req.headers), _signed_headers(req.headers)]
64+
_canonical_headers(signed_headers), _get_signed_header_key(signed_headers)]
5565
if req.headers.get('x-tos-content-sha256'):
5666
cr.append(req.headers['x-tos-content-sha256'])
5767
else:
@@ -134,8 +144,9 @@ def sign_request(self, req):
134144
req.headers['Date'] = date
135145
req.headers['x-tos-date'] = date
136146

137-
signature = self._make_signature(req=req, date=date)
138-
req.headers['Authorization'] = self._inject_signature_to_request(req, signature, date)
147+
signed_headers = _get_signed_headers(req.headers)
148+
signature = self._make_signature(req=req, date=date, signed_headers=signed_headers)
149+
req.headers['Authorization'] = self._inject_signature_to_request(signature, date, signed_headers)
139150

140151
def sign_url(self, req, expires):
141152
if expires is None:
@@ -147,11 +158,11 @@ def sign_url(self, req, expires):
147158
req.params['X-Tos-Credential'] = self._credential(date)
148159
req.params['X-Tos-Date'] = date
149160
req.params['X-Tos-Expires'] = expires
150-
req.params['X-Tos-SignedHeaders'] = _signed_headers(req.headers)
151-
152161
if self.credential.get_security_token():
153162
req.params["X-Tos-Security-Token"] = self.credential.get_security_token()
154-
req.params['X-Tos-Signature'] = self._make_signature(req=req, date=date)
163+
signed_headers = _get_signed_headers(req.headers, True)
164+
req.params['X-Tos-SignedHeaders'] = _get_signed_header_key(signed_headers)
165+
req.params['X-Tos-Signature'] = self._make_signature(req=req, date=date, signed_headers=signed_headers)
155166

156167
return req.url + '?' + '&'.join(_param_to_quoted_query(k, v) for k, v in req.params.items())
157168

@@ -190,9 +201,9 @@ def x_tos_post_sign(self, expires: int, conditions: []):
190201

191202
return '&'.join(_param_to_quoted_query(k, v) for k, v in params.items())
192203

193-
def _make_signature(self, date, req=None, string_to_sign=None):
204+
def _make_signature(self, date, req=None, string_to_sign=None, signed_headers=None):
194205
if not string_to_sign:
195-
canonical_request = _canonical_request(req)
206+
canonical_request = _canonical_request(req, signed_headers)
196207
logger.debug("pre-request: canonical_request:\n%s", canonical_request)
197208
string_to_sign = self._string_to_sign(canonical_request, date)
198209
logger.debug("pre-request: string_to_sign:\n%s", string_to_sign)
@@ -209,9 +220,9 @@ def _make_x_tos_policy_signature(self, date, params):
209220
logger.debug("pre-request: signature:\n%s", signature)
210221
return signature
211222

212-
def _inject_signature_to_request(self, req, signature, date):
223+
def _inject_signature_to_request(self, signature, date, signed_headers):
213224
results = ['TOS4-HMAC-SHA256 Credential=%s' % self._credential(date),
214-
'SignedHeaders=%s' % _signed_headers(req.headers), 'Signature=%s' % signature]
225+
'SignedHeaders=%s' % _get_signed_header_key(signed_headers), 'Signature=%s' % signature]
215226
return ', '.join(results)
216227

217228
def _string_to_sign(self, canonical_request, date):

0 commit comments

Comments
 (0)