signers.py 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734
  1. # Copyright 2014 Amazon.com, Inc. or its affiliates. All Rights Reserved.
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License"). You
  4. # may not use this file except in compliance with the License. A copy of
  5. # the License is located at
  6. #
  7. # http://aws.amazon.com/apache2.0/
  8. #
  9. # or in the "license" file accompanying this file. This file is
  10. # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
  11. # ANY KIND, either express or implied. See the License for the specific
  12. # language governing permissions and limitations under the License.
  13. import datetime
  14. import weakref
  15. import json
  16. import base64
  17. import botocore
  18. import botocore.auth
  19. from botocore.compat import six, OrderedDict
  20. from botocore.awsrequest import create_request_object, prepare_request_dict
  21. from botocore.exceptions import UnknownSignatureVersionError
  22. from botocore.exceptions import UnknownClientMethodError
  23. from botocore.exceptions import UnsupportedSignatureVersionError
  24. from botocore.utils import fix_s3_host, datetime2timestamp
  25. class RequestSigner(object):
  26. """
  27. An object to sign requests before they go out over the wire using
  28. one of the authentication mechanisms defined in ``auth.py``. This
  29. class fires two events scoped to a service and operation name:
  30. * choose-signer: Allows overriding the auth signer name.
  31. * before-sign: Allows mutating the request before signing.
  32. Together these events allow for customization of the request
  33. signing pipeline, including overrides, request path manipulation,
  34. and disabling signing per operation.
  35. :type service_id: botocore.model.ServiceId
  36. :param service_id: The service id for the service, e.g. ``S3``
  37. :type region_name: string
  38. :param region_name: Name of the service region, e.g. ``us-east-1``
  39. :type signing_name: string
  40. :param signing_name: Service signing name. This is usually the
  41. same as the service name, but can differ. E.g.
  42. ``emr`` vs. ``elasticmapreduce``.
  43. :type signature_version: string
  44. :param signature_version: Signature name like ``v4``.
  45. :type credentials: :py:class:`~botocore.credentials.Credentials`
  46. :param credentials: User credentials with which to sign requests.
  47. :type event_emitter: :py:class:`~botocore.hooks.BaseEventHooks`
  48. :param event_emitter: Extension mechanism to fire events.
  49. """
  50. def __init__(self, service_id, region_name, signing_name,
  51. signature_version, credentials, event_emitter):
  52. self._region_name = region_name
  53. self._signing_name = signing_name
  54. self._signature_version = signature_version
  55. self._credentials = credentials
  56. self._service_id = service_id
  57. # We need weakref to prevent leaking memory in Python 2.6 on Linux 2.6
  58. self._event_emitter = weakref.proxy(event_emitter)
  59. @property
  60. def region_name(self):
  61. return self._region_name
  62. @property
  63. def signature_version(self):
  64. return self._signature_version
  65. @property
  66. def signing_name(self):
  67. return self._signing_name
  68. def handler(self, operation_name=None, request=None, **kwargs):
  69. # This is typically hooked up to the "request-created" event
  70. # from a client's event emitter. When a new request is created
  71. # this method is invoked to sign the request.
  72. # Don't call this method directly.
  73. return self.sign(operation_name, request)
  74. def sign(self, operation_name, request, region_name=None,
  75. signing_type='standard', expires_in=None, signing_name=None):
  76. """Sign a request before it goes out over the wire.
  77. :type operation_name: string
  78. :param operation_name: The name of the current operation, e.g.
  79. ``ListBuckets``.
  80. :type request: AWSRequest
  81. :param request: The request object to be sent over the wire.
  82. :type region_name: str
  83. :param region_name: The region to sign the request for.
  84. :type signing_type: str
  85. :param signing_type: The type of signing to perform. This can be one of
  86. three possible values:
  87. * 'standard' - This should be used for most requests.
  88. * 'presign-url' - This should be used when pre-signing a request.
  89. * 'presign-post' - This should be used when pre-signing an S3 post.
  90. :type expires_in: int
  91. :param expires_in: The number of seconds the presigned url is valid
  92. for. This parameter is only valid for signing type 'presign-url'.
  93. :type signing_name: str
  94. :param signing_name: The name to use for the service when signing.
  95. """
  96. explicit_region_name = region_name
  97. if region_name is None:
  98. region_name = self._region_name
  99. if signing_name is None:
  100. signing_name = self._signing_name
  101. signature_version = self._choose_signer(
  102. operation_name, signing_type, request.context)
  103. # Allow mutating request before signing
  104. self._event_emitter.emit(
  105. 'before-sign.{0}.{1}'.format(
  106. self._service_id.hyphenize(), operation_name),
  107. request=request, signing_name=signing_name,
  108. region_name=self._region_name,
  109. signature_version=signature_version, request_signer=self,
  110. operation_name=operation_name
  111. )
  112. if signature_version != botocore.UNSIGNED:
  113. kwargs = {
  114. 'signing_name': signing_name,
  115. 'region_name': region_name,
  116. 'signature_version': signature_version
  117. }
  118. if expires_in is not None:
  119. kwargs['expires'] = expires_in
  120. signing_context = request.context.get('signing', {})
  121. if not explicit_region_name and signing_context.get('region'):
  122. kwargs['region_name'] = signing_context['region']
  123. if signing_context.get('signing_name'):
  124. kwargs['signing_name'] = signing_context['signing_name']
  125. try:
  126. auth = self.get_auth_instance(**kwargs)
  127. except UnknownSignatureVersionError as e:
  128. if signing_type != 'standard':
  129. raise UnsupportedSignatureVersionError(
  130. signature_version=signature_version)
  131. else:
  132. raise e
  133. auth.add_auth(request)
  134. def _choose_signer(self, operation_name, signing_type, context):
  135. """
  136. Allow setting the signature version via the choose-signer event.
  137. A value of `botocore.UNSIGNED` means no signing will be performed.
  138. :param operation_name: The operation to sign.
  139. :param signing_type: The type of signing that the signer is to be used
  140. for.
  141. :return: The signature version to sign with.
  142. """
  143. signing_type_suffix_map = {
  144. 'presign-post': '-presign-post',
  145. 'presign-url': '-query'
  146. }
  147. suffix = signing_type_suffix_map.get(signing_type, '')
  148. signature_version = self._signature_version
  149. if signature_version is not botocore.UNSIGNED and not \
  150. signature_version.endswith(suffix):
  151. signature_version += suffix
  152. handler, response = self._event_emitter.emit_until_response(
  153. 'choose-signer.{0}.{1}'.format(
  154. self._service_id.hyphenize(), operation_name),
  155. signing_name=self._signing_name, region_name=self._region_name,
  156. signature_version=signature_version, context=context)
  157. if response is not None:
  158. signature_version = response
  159. # The suffix needs to be checked again in case we get an improper
  160. # signature version from choose-signer.
  161. if signature_version is not botocore.UNSIGNED and not \
  162. signature_version.endswith(suffix):
  163. signature_version += suffix
  164. return signature_version
  165. def get_auth_instance(self, signing_name, region_name,
  166. signature_version=None, **kwargs):
  167. """
  168. Get an auth instance which can be used to sign a request
  169. using the given signature version.
  170. :type signing_name: string
  171. :param signing_name: Service signing name. This is usually the
  172. same as the service name, but can differ. E.g.
  173. ``emr`` vs. ``elasticmapreduce``.
  174. :type region_name: string
  175. :param region_name: Name of the service region, e.g. ``us-east-1``
  176. :type signature_version: string
  177. :param signature_version: Signature name like ``v4``.
  178. :rtype: :py:class:`~botocore.auth.BaseSigner`
  179. :return: Auth instance to sign a request.
  180. """
  181. if signature_version is None:
  182. signature_version = self._signature_version
  183. cls = botocore.auth.AUTH_TYPE_MAPS.get(signature_version)
  184. if cls is None:
  185. raise UnknownSignatureVersionError(
  186. signature_version=signature_version)
  187. # If there's no credentials provided (i.e credentials is None),
  188. # then we'll pass a value of "None" over to the auth classes,
  189. # which already handle the cases where no credentials have
  190. # been provided.
  191. frozen_credentials = None
  192. if self._credentials is not None:
  193. frozen_credentials = self._credentials.get_frozen_credentials()
  194. kwargs['credentials'] = frozen_credentials
  195. if cls.REQUIRES_REGION:
  196. if self._region_name is None:
  197. raise botocore.exceptions.NoRegionError()
  198. kwargs['region_name'] = region_name
  199. kwargs['service_name'] = signing_name
  200. auth = cls(**kwargs)
  201. return auth
  202. # Alias get_auth for backwards compatibility.
  203. get_auth = get_auth_instance
  204. def generate_presigned_url(self, request_dict, operation_name,
  205. expires_in=3600, region_name=None,
  206. signing_name=None):
  207. """Generates a presigned url
  208. :type request_dict: dict
  209. :param request_dict: The prepared request dictionary returned by
  210. ``botocore.awsrequest.prepare_request_dict()``
  211. :type operation_name: str
  212. :param operation_name: The operation being signed.
  213. :type expires_in: int
  214. :param expires_in: The number of seconds the presigned url is valid
  215. for. By default it expires in an hour (3600 seconds)
  216. :type region_name: string
  217. :param region_name: The region name to sign the presigned url.
  218. :type signing_name: str
  219. :param signing_name: The name to use for the service when signing.
  220. :returns: The presigned url
  221. """
  222. request = create_request_object(request_dict)
  223. self.sign(operation_name, request, region_name,
  224. 'presign-url', expires_in, signing_name)
  225. request.prepare()
  226. return request.url
  227. class CloudFrontSigner(object):
  228. '''A signer to create a signed CloudFront URL.
  229. First you create a cloudfront signer based on a normalized RSA signer::
  230. import rsa
  231. def rsa_signer(message):
  232. private_key = open('private_key.pem', 'r').read()
  233. return rsa.sign(
  234. message,
  235. rsa.PrivateKey.load_pkcs1(private_key.encode('utf8')),
  236. 'SHA-1') # CloudFront requires SHA-1 hash
  237. cf_signer = CloudFrontSigner(key_id, rsa_signer)
  238. To sign with a canned policy::
  239. signed_url = cf_signer.generate_signed_url(
  240. url, date_less_than=datetime(2015, 12, 1))
  241. To sign with a custom policy::
  242. signed_url = cf_signer.generate_signed_url(url, policy=my_policy)
  243. '''
  244. def __init__(self, key_id, rsa_signer):
  245. """Create a CloudFrontSigner.
  246. :type key_id: str
  247. :param key_id: The CloudFront Key Pair ID
  248. :type rsa_signer: callable
  249. :param rsa_signer: An RSA signer.
  250. Its only input parameter will be the message to be signed,
  251. and its output will be the signed content as a binary string.
  252. The hash algorithm needed by CloudFront is SHA-1.
  253. """
  254. self.key_id = key_id
  255. self.rsa_signer = rsa_signer
  256. def generate_presigned_url(self, url, date_less_than=None, policy=None):
  257. """Creates a signed CloudFront URL based on given parameters.
  258. :type url: str
  259. :param url: The URL of the protected object
  260. :type date_less_than: datetime
  261. :param date_less_than: The URL will expire after that date and time
  262. :type policy: str
  263. :param policy: The custom policy, possibly built by self.build_policy()
  264. :rtype: str
  265. :return: The signed URL.
  266. """
  267. if (date_less_than is not None and policy is not None or
  268. date_less_than is None and policy is None):
  269. e = 'Need to provide either date_less_than or policy, but not both'
  270. raise ValueError(e)
  271. if date_less_than is not None:
  272. # We still need to build a canned policy for signing purpose
  273. policy = self.build_policy(url, date_less_than)
  274. if isinstance(policy, six.text_type):
  275. policy = policy.encode('utf8')
  276. if date_less_than is not None:
  277. params = ['Expires=%s' % int(datetime2timestamp(date_less_than))]
  278. else:
  279. params = ['Policy=%s' % self._url_b64encode(policy).decode('utf8')]
  280. signature = self.rsa_signer(policy)
  281. params.extend([
  282. 'Signature=%s' % self._url_b64encode(signature).decode('utf8'),
  283. 'Key-Pair-Id=%s' % self.key_id,
  284. ])
  285. return self._build_url(url, params)
  286. def _build_url(self, base_url, extra_params):
  287. separator = '&' if '?' in base_url else '?'
  288. return base_url + separator + '&'.join(extra_params)
  289. def build_policy(self, resource, date_less_than,
  290. date_greater_than=None, ip_address=None):
  291. """A helper to build policy.
  292. :type resource: str
  293. :param resource: The URL or the stream filename of the protected object
  294. :type date_less_than: datetime
  295. :param date_less_than: The URL will expire after the time has passed
  296. :type date_greater_than: datetime
  297. :param date_greater_than: The URL will not be valid until this time
  298. :type ip_address: str
  299. :param ip_address: Use 'x.x.x.x' for an IP, or 'x.x.x.x/x' for a subnet
  300. :rtype: str
  301. :return: The policy in a compact string.
  302. """
  303. # Note:
  304. # 1. Order in canned policy is significant. Special care has been taken
  305. # to ensure the output will match the order defined by the document.
  306. # There is also a test case to ensure that order.
  307. # SEE: http://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-creating-signed-url-canned-policy.html#private-content-canned-policy-creating-policy-statement
  308. # 2. Albeit the order in custom policy is not required by CloudFront,
  309. # we still use OrderedDict internally to ensure the result is stable
  310. # and also matches canned policy requirement.
  311. # SEE: http://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-creating-signed-url-custom-policy.html
  312. moment = int(datetime2timestamp(date_less_than))
  313. condition = OrderedDict({"DateLessThan": {"AWS:EpochTime": moment}})
  314. if ip_address:
  315. if '/' not in ip_address:
  316. ip_address += '/32'
  317. condition["IpAddress"] = {"AWS:SourceIp": ip_address}
  318. if date_greater_than:
  319. moment = int(datetime2timestamp(date_greater_than))
  320. condition["DateGreaterThan"] = {"AWS:EpochTime": moment}
  321. ordered_payload = [('Resource', resource), ('Condition', condition)]
  322. custom_policy = {"Statement": [OrderedDict(ordered_payload)]}
  323. return json.dumps(custom_policy, separators=(',', ':'))
  324. def _url_b64encode(self, data):
  325. # Required by CloudFront. See also:
  326. # http://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-linux-openssl.html
  327. return base64.b64encode(
  328. data).replace(b'+', b'-').replace(b'=', b'_').replace(b'/', b'~')
  329. def add_generate_db_auth_token(class_attributes, **kwargs):
  330. class_attributes['generate_db_auth_token'] = generate_db_auth_token
  331. def generate_db_auth_token(self, DBHostname, Port, DBUsername, Region=None):
  332. """Generates an auth token used to connect to a db with IAM credentials.
  333. :type DBHostname: str
  334. :param DBHostname: The hostname of the database to connect to.
  335. :type Port: int
  336. :param Port: The port number the database is listening on.
  337. :type DBUsername: str
  338. :param DBUsername: The username to log in as.
  339. :type Region: str
  340. :param Region: The region the database is in. If None, the client
  341. region will be used.
  342. :return: A presigned url which can be used as an auth token.
  343. """
  344. region = Region
  345. if region is None:
  346. region = self.meta.region_name
  347. params = {
  348. 'Action': 'connect',
  349. 'DBUser': DBUsername,
  350. }
  351. request_dict = {
  352. 'url_path': '/',
  353. 'query_string': '',
  354. 'headers': {},
  355. 'body': params,
  356. 'method': 'GET'
  357. }
  358. # RDS requires that the scheme not be set when sent over. This can cause
  359. # issues when signing because the Python url parsing libraries follow
  360. # RFC 1808 closely, which states that a netloc must be introduced by `//`.
  361. # Otherwise the url is presumed to be relative, and thus the whole
  362. # netloc would be treated as a path component. To work around this we
  363. # introduce https here and remove it once we're done processing it.
  364. scheme = 'https://'
  365. endpoint_url = '%s%s:%s' % (scheme, DBHostname, Port)
  366. prepare_request_dict(request_dict, endpoint_url)
  367. presigned_url = self._request_signer.generate_presigned_url(
  368. operation_name='connect', request_dict=request_dict,
  369. region_name=region, expires_in=900, signing_name='rds-db'
  370. )
  371. return presigned_url[len(scheme):]
  372. class S3PostPresigner(object):
  373. def __init__(self, request_signer):
  374. self._request_signer = request_signer
  375. def generate_presigned_post(self, request_dict, fields=None,
  376. conditions=None, expires_in=3600,
  377. region_name=None):
  378. """Generates the url and the form fields used for a presigned s3 post
  379. :type request_dict: dict
  380. :param request_dict: The prepared request dictionary returned by
  381. ``botocore.awsrequest.prepare_request_dict()``
  382. :type fields: dict
  383. :param fields: A dictionary of prefilled form fields to build on top
  384. of.
  385. :type conditions: list
  386. :param conditions: A list of conditions to include in the policy. Each
  387. element can be either a list or a structure. For example:
  388. [
  389. {"acl": "public-read"},
  390. {"bucket": "mybucket"},
  391. ["starts-with", "$key", "mykey"]
  392. ]
  393. :type expires_in: int
  394. :param expires_in: The number of seconds the presigned post is valid
  395. for.
  396. :type region_name: string
  397. :param region_name: The region name to sign the presigned post to.
  398. :rtype: dict
  399. :returns: A dictionary with two elements: ``url`` and ``fields``.
  400. Url is the url to post to. Fields is a dictionary filled with
  401. the form fields and respective values to use when submitting the
  402. post. For example:
  403. {'url': 'https://mybucket.s3.amazonaws.com
  404. 'fields': {'acl': 'public-read',
  405. 'key': 'mykey',
  406. 'signature': 'mysignature',
  407. 'policy': 'mybase64 encoded policy'}
  408. }
  409. """
  410. if fields is None:
  411. fields = {}
  412. if conditions is None:
  413. conditions = []
  414. # Create the policy for the post.
  415. policy = {}
  416. # Create an expiration date for the policy
  417. datetime_now = datetime.datetime.utcnow()
  418. expire_date = datetime_now + datetime.timedelta(seconds=expires_in)
  419. policy['expiration'] = expire_date.strftime(botocore.auth.ISO8601)
  420. # Append all of the conditions that the user supplied.
  421. policy['conditions'] = []
  422. for condition in conditions:
  423. policy['conditions'].append(condition)
  424. # Store the policy and the fields in the request for signing
  425. request = create_request_object(request_dict)
  426. request.context['s3-presign-post-fields'] = fields
  427. request.context['s3-presign-post-policy'] = policy
  428. self._request_signer.sign(
  429. 'PutObject', request, region_name, 'presign-post')
  430. # Return the url and the fields for th form to post.
  431. return {'url': request.url, 'fields': fields}
  432. def add_generate_presigned_url(class_attributes, **kwargs):
  433. class_attributes['generate_presigned_url'] = generate_presigned_url
  434. def generate_presigned_url(self, ClientMethod, Params=None, ExpiresIn=3600,
  435. HttpMethod=None):
  436. """Generate a presigned url given a client, its method, and arguments
  437. :type ClientMethod: string
  438. :param ClientMethod: The client method to presign for
  439. :type Params: dict
  440. :param Params: The parameters normally passed to
  441. ``ClientMethod``.
  442. :type ExpiresIn: int
  443. :param ExpiresIn: The number of seconds the presigned url is valid
  444. for. By default it expires in an hour (3600 seconds)
  445. :type HttpMethod: string
  446. :param HttpMethod: The http method to use on the generated url. By
  447. default, the http method is whatever is used in the method's model.
  448. :returns: The presigned url
  449. """
  450. client_method = ClientMethod
  451. params = Params
  452. if params is None:
  453. params = {}
  454. expires_in = ExpiresIn
  455. http_method = HttpMethod
  456. context = {
  457. 'is_presign_request': True,
  458. 'use_global_endpoint': _should_use_global_endpoint(self),
  459. }
  460. request_signer = self._request_signer
  461. serializer = self._serializer
  462. try:
  463. operation_name = self._PY_TO_OP_NAME[client_method]
  464. except KeyError:
  465. raise UnknownClientMethodError(method_name=client_method)
  466. operation_model = self.meta.service_model.operation_model(
  467. operation_name)
  468. params = self._emit_api_params(params, operation_model, context)
  469. # Create a request dict based on the params to serialize.
  470. request_dict = serializer.serialize_to_request(
  471. params, operation_model)
  472. # Switch out the http method if user specified it.
  473. if http_method is not None:
  474. request_dict['method'] = http_method
  475. # Prepare the request dict by including the client's endpoint url.
  476. prepare_request_dict(
  477. request_dict, endpoint_url=self.meta.endpoint_url, context=context)
  478. # Generate the presigned url.
  479. return request_signer.generate_presigned_url(
  480. request_dict=request_dict, expires_in=expires_in,
  481. operation_name=operation_name)
  482. def add_generate_presigned_post(class_attributes, **kwargs):
  483. class_attributes['generate_presigned_post'] = generate_presigned_post
  484. def generate_presigned_post(self, Bucket, Key, Fields=None, Conditions=None,
  485. ExpiresIn=3600):
  486. """Builds the url and the form fields used for a presigned s3 post
  487. :type Bucket: string
  488. :param Bucket: The name of the bucket to presign the post to. Note that
  489. bucket related conditions should not be included in the
  490. ``conditions`` parameter.
  491. :type Key: string
  492. :param Key: Key name, optionally add ${filename} to the end to
  493. attach the submitted filename. Note that key related conditions and
  494. fields are filled out for you and should not be included in the
  495. ``Fields`` or ``Conditions`` parameter.
  496. :type Fields: dict
  497. :param Fields: A dictionary of prefilled form fields to build on top
  498. of. Elements that may be included are acl, Cache-Control,
  499. Content-Type, Content-Disposition, Content-Encoding, Expires,
  500. success_action_redirect, redirect, success_action_status,
  501. and x-amz-meta-.
  502. Note that if a particular element is included in the fields
  503. dictionary it will not be automatically added to the conditions
  504. list. You must specify a condition for the element as well.
  505. :type Conditions: list
  506. :param Conditions: A list of conditions to include in the policy. Each
  507. element can be either a list or a structure. For example:
  508. [
  509. {"acl": "public-read"},
  510. ["content-length-range", 2, 5],
  511. ["starts-with", "$success_action_redirect", ""]
  512. ]
  513. Conditions that are included may pertain to acl,
  514. content-length-range, Cache-Control, Content-Type,
  515. Content-Disposition, Content-Encoding, Expires,
  516. success_action_redirect, redirect, success_action_status,
  517. and/or x-amz-meta-.
  518. Note that if you include a condition, you must specify
  519. the a valid value in the fields dictionary as well. A value will
  520. not be added automatically to the fields dictionary based on the
  521. conditions.
  522. :type ExpiresIn: int
  523. :param ExpiresIn: The number of seconds the presigned post
  524. is valid for.
  525. :rtype: dict
  526. :returns: A dictionary with two elements: ``url`` and ``fields``.
  527. Url is the url to post to. Fields is a dictionary filled with
  528. the form fields and respective values to use when submitting the
  529. post. For example:
  530. {'url': 'https://mybucket.s3.amazonaws.com
  531. 'fields': {'acl': 'public-read',
  532. 'key': 'mykey',
  533. 'signature': 'mysignature',
  534. 'policy': 'mybase64 encoded policy'}
  535. }
  536. """
  537. bucket = Bucket
  538. key = Key
  539. fields = Fields
  540. conditions = Conditions
  541. expires_in = ExpiresIn
  542. if fields is None:
  543. fields = {}
  544. else:
  545. fields = fields.copy()
  546. if conditions is None:
  547. conditions = []
  548. post_presigner = S3PostPresigner(self._request_signer)
  549. serializer = self._serializer
  550. # We choose the CreateBucket operation model because its url gets
  551. # serialized to what a presign post requires.
  552. operation_model = self.meta.service_model.operation_model(
  553. 'CreateBucket')
  554. # Create a request dict based on the params to serialize.
  555. request_dict = serializer.serialize_to_request(
  556. {'Bucket': bucket}, operation_model)
  557. # Prepare the request dict by including the client's endpoint url.
  558. prepare_request_dict(
  559. request_dict, endpoint_url=self.meta.endpoint_url,
  560. context={
  561. 'is_presign_request': True,
  562. 'use_global_endpoint': _should_use_global_endpoint(self),
  563. },
  564. )
  565. # Append that the bucket name to the list of conditions.
  566. conditions.append({'bucket': bucket})
  567. # If the key ends with filename, the only constraint that can be
  568. # imposed is if it starts with the specified prefix.
  569. if key.endswith('${filename}'):
  570. conditions.append(["starts-with", '$key', key[:-len('${filename}')]])
  571. else:
  572. conditions.append({'key': key})
  573. # Add the key to the fields.
  574. fields['key'] = key
  575. return post_presigner.generate_presigned_post(
  576. request_dict=request_dict, fields=fields, conditions=conditions,
  577. expires_in=expires_in)
  578. def _should_use_global_endpoint(client):
  579. if client.meta.partition != 'aws':
  580. return False
  581. s3_config = client.meta.config.s3
  582. if s3_config:
  583. if s3_config.get('use_dualstack_endpoint', False):
  584. return False
  585. if s3_config.get('us_east_1_regional_endpoint') == 'regional' and \
  586. client.meta.config.region_name == 'us-east-1':
  587. return False
  588. return True