Mercurial > repos > guerler > springsuite
comparison planemo/lib/python3.7/site-packages/boto/cloudfront/distribution.py @ 0:d30785e31577 draft
"planemo upload commit 6eee67778febed82ddd413c3ca40b3183a3898f1"
| author | guerler |
|---|---|
| date | Fri, 31 Jul 2020 00:18:57 -0400 |
| parents | |
| children |
comparison
equal
deleted
inserted
replaced
| -1:000000000000 | 0:d30785e31577 |
|---|---|
| 1 # Copyright (c) 2006-2009 Mitch Garnaat http://garnaat.org/ | |
| 2 # | |
| 3 # Permission is hereby granted, free of charge, to any person obtaining a | |
| 4 # copy of this software and associated documentation files (the | |
| 5 # "Software"), to deal in the Software without restriction, including | |
| 6 # without limitation the rights to use, copy, modify, merge, publish, dis- | |
| 7 # tribute, sublicense, and/or sell copies of the Software, and to permit | |
| 8 # persons to whom the Software is furnished to do so, subject to the fol- | |
| 9 # lowing conditions: | |
| 10 # | |
| 11 # The above copyright notice and this permission notice shall be included | |
| 12 # in all copies or substantial portions of the Software. | |
| 13 # | |
| 14 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS | |
| 15 # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL- | |
| 16 # ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT | |
| 17 # SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, | |
| 18 # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
| 19 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS | |
| 20 # IN THE SOFTWARE. | |
| 21 | |
| 22 import uuid | |
| 23 import base64 | |
| 24 import time | |
| 25 from boto.compat import six, json | |
| 26 from boto.cloudfront.identity import OriginAccessIdentity | |
| 27 from boto.cloudfront.object import Object, StreamingObject | |
| 28 from boto.cloudfront.signers import ActiveTrustedSigners, TrustedSigners | |
| 29 from boto.cloudfront.logging import LoggingInfo | |
| 30 from boto.cloudfront.origin import S3Origin, CustomOrigin | |
| 31 from boto.s3.acl import ACL | |
| 32 | |
| 33 class DistributionConfig(object): | |
| 34 | |
| 35 def __init__(self, connection=None, origin=None, enabled=False, | |
| 36 caller_reference='', cnames=None, comment='', | |
| 37 trusted_signers=None, default_root_object=None, | |
| 38 logging=None): | |
| 39 """ | |
| 40 :param origin: Origin information to associate with the | |
| 41 distribution. If your distribution will use | |
| 42 an Amazon S3 origin, then this should be an | |
| 43 S3Origin object. If your distribution will use | |
| 44 a custom origin (non Amazon S3), then this | |
| 45 should be a CustomOrigin object. | |
| 46 :type origin: :class:`boto.cloudfront.origin.S3Origin` or | |
| 47 :class:`boto.cloudfront.origin.CustomOrigin` | |
| 48 | |
| 49 :param enabled: Whether the distribution is enabled to accept | |
| 50 end user requests for content. | |
| 51 :type enabled: bool | |
| 52 | |
| 53 :param caller_reference: A unique number that ensures the | |
| 54 request can't be replayed. If no | |
| 55 caller_reference is provided, boto | |
| 56 will generate a type 4 UUID for use | |
| 57 as the caller reference. | |
| 58 :type enabled: str | |
| 59 | |
| 60 :param cnames: A CNAME alias you want to associate with this | |
| 61 distribution. You can have up to 10 CNAME aliases | |
| 62 per distribution. | |
| 63 :type enabled: array of str | |
| 64 | |
| 65 :param comment: Any comments you want to include about the | |
| 66 distribution. | |
| 67 :type comment: str | |
| 68 | |
| 69 :param trusted_signers: Specifies any AWS accounts you want to | |
| 70 permit to create signed URLs for private | |
| 71 content. If you want the distribution to | |
| 72 use signed URLs, this should contain a | |
| 73 TrustedSigners object; if you want the | |
| 74 distribution to use basic URLs, leave | |
| 75 this None. | |
| 76 :type trusted_signers: :class`boto.cloudfront.signers.TrustedSigners` | |
| 77 | |
| 78 :param default_root_object: Designates a default root object. | |
| 79 Only include a DefaultRootObject value | |
| 80 if you are going to assign a default | |
| 81 root object for the distribution. | |
| 82 :type comment: str | |
| 83 | |
| 84 :param logging: Controls whether access logs are written for the | |
| 85 distribution. If you want to turn on access logs, | |
| 86 this should contain a LoggingInfo object; otherwise | |
| 87 it should contain None. | |
| 88 :type logging: :class`boto.cloudfront.logging.LoggingInfo` | |
| 89 | |
| 90 """ | |
| 91 self.connection = connection | |
| 92 self.origin = origin | |
| 93 self.enabled = enabled | |
| 94 if caller_reference: | |
| 95 self.caller_reference = caller_reference | |
| 96 else: | |
| 97 self.caller_reference = str(uuid.uuid4()) | |
| 98 self.cnames = [] | |
| 99 if cnames: | |
| 100 self.cnames = cnames | |
| 101 self.comment = comment | |
| 102 self.trusted_signers = trusted_signers | |
| 103 self.logging = logging | |
| 104 self.default_root_object = default_root_object | |
| 105 | |
| 106 def __repr__(self): | |
| 107 return "DistributionConfig:%s" % self.origin | |
| 108 | |
| 109 def to_xml(self): | |
| 110 s = '<?xml version="1.0" encoding="UTF-8"?>\n' | |
| 111 s += '<DistributionConfig xmlns="http://cloudfront.amazonaws.com/doc/2010-07-15/">\n' | |
| 112 if self.origin: | |
| 113 s += self.origin.to_xml() | |
| 114 s += ' <CallerReference>%s</CallerReference>\n' % self.caller_reference | |
| 115 for cname in self.cnames: | |
| 116 s += ' <CNAME>%s</CNAME>\n' % cname | |
| 117 if self.comment: | |
| 118 s += ' <Comment>%s</Comment>\n' % self.comment | |
| 119 s += ' <Enabled>' | |
| 120 if self.enabled: | |
| 121 s += 'true' | |
| 122 else: | |
| 123 s += 'false' | |
| 124 s += '</Enabled>\n' | |
| 125 if self.trusted_signers: | |
| 126 s += '<TrustedSigners>\n' | |
| 127 for signer in self.trusted_signers: | |
| 128 if signer == 'Self': | |
| 129 s += ' <Self></Self>\n' | |
| 130 else: | |
| 131 s += ' <AwsAccountNumber>%s</AwsAccountNumber>\n' % signer | |
| 132 s += '</TrustedSigners>\n' | |
| 133 if self.logging: | |
| 134 s += '<Logging>\n' | |
| 135 s += ' <Bucket>%s</Bucket>\n' % self.logging.bucket | |
| 136 s += ' <Prefix>%s</Prefix>\n' % self.logging.prefix | |
| 137 s += '</Logging>\n' | |
| 138 if self.default_root_object: | |
| 139 dro = self.default_root_object | |
| 140 s += '<DefaultRootObject>%s</DefaultRootObject>\n' % dro | |
| 141 s += '</DistributionConfig>\n' | |
| 142 return s | |
| 143 | |
| 144 def startElement(self, name, attrs, connection): | |
| 145 if name == 'TrustedSigners': | |
| 146 self.trusted_signers = TrustedSigners() | |
| 147 return self.trusted_signers | |
| 148 elif name == 'Logging': | |
| 149 self.logging = LoggingInfo() | |
| 150 return self.logging | |
| 151 elif name == 'S3Origin': | |
| 152 self.origin = S3Origin() | |
| 153 return self.origin | |
| 154 elif name == 'CustomOrigin': | |
| 155 self.origin = CustomOrigin() | |
| 156 return self.origin | |
| 157 else: | |
| 158 return None | |
| 159 | |
| 160 def endElement(self, name, value, connection): | |
| 161 if name == 'CNAME': | |
| 162 self.cnames.append(value) | |
| 163 elif name == 'Comment': | |
| 164 self.comment = value | |
| 165 elif name == 'Enabled': | |
| 166 if value.lower() == 'true': | |
| 167 self.enabled = True | |
| 168 else: | |
| 169 self.enabled = False | |
| 170 elif name == 'CallerReference': | |
| 171 self.caller_reference = value | |
| 172 elif name == 'DefaultRootObject': | |
| 173 self.default_root_object = value | |
| 174 else: | |
| 175 setattr(self, name, value) | |
| 176 | |
| 177 class StreamingDistributionConfig(DistributionConfig): | |
| 178 | |
| 179 def __init__(self, connection=None, origin='', enabled=False, | |
| 180 caller_reference='', cnames=None, comment='', | |
| 181 trusted_signers=None, logging=None): | |
| 182 super(StreamingDistributionConfig, self).__init__(connection=connection, | |
| 183 origin=origin, enabled=enabled, | |
| 184 caller_reference=caller_reference, | |
| 185 cnames=cnames, comment=comment, | |
| 186 trusted_signers=trusted_signers, | |
| 187 logging=logging) | |
| 188 def to_xml(self): | |
| 189 s = '<?xml version="1.0" encoding="UTF-8"?>\n' | |
| 190 s += '<StreamingDistributionConfig xmlns="http://cloudfront.amazonaws.com/doc/2010-07-15/">\n' | |
| 191 if self.origin: | |
| 192 s += self.origin.to_xml() | |
| 193 s += ' <CallerReference>%s</CallerReference>\n' % self.caller_reference | |
| 194 for cname in self.cnames: | |
| 195 s += ' <CNAME>%s</CNAME>\n' % cname | |
| 196 if self.comment: | |
| 197 s += ' <Comment>%s</Comment>\n' % self.comment | |
| 198 s += ' <Enabled>' | |
| 199 if self.enabled: | |
| 200 s += 'true' | |
| 201 else: | |
| 202 s += 'false' | |
| 203 s += '</Enabled>\n' | |
| 204 if self.trusted_signers: | |
| 205 s += '<TrustedSigners>\n' | |
| 206 for signer in self.trusted_signers: | |
| 207 if signer == 'Self': | |
| 208 s += ' <Self/>\n' | |
| 209 else: | |
| 210 s += ' <AwsAccountNumber>%s</AwsAccountNumber>\n' % signer | |
| 211 s += '</TrustedSigners>\n' | |
| 212 if self.logging: | |
| 213 s += '<Logging>\n' | |
| 214 s += ' <Bucket>%s</Bucket>\n' % self.logging.bucket | |
| 215 s += ' <Prefix>%s</Prefix>\n' % self.logging.prefix | |
| 216 s += '</Logging>\n' | |
| 217 s += '</StreamingDistributionConfig>\n' | |
| 218 return s | |
| 219 | |
| 220 class DistributionSummary(object): | |
| 221 | |
| 222 def __init__(self, connection=None, domain_name='', id='', | |
| 223 last_modified_time=None, status='', origin=None, | |
| 224 cname='', comment='', enabled=False): | |
| 225 self.connection = connection | |
| 226 self.domain_name = domain_name | |
| 227 self.id = id | |
| 228 self.last_modified_time = last_modified_time | |
| 229 self.status = status | |
| 230 self.origin = origin | |
| 231 self.enabled = enabled | |
| 232 self.cnames = [] | |
| 233 if cname: | |
| 234 self.cnames.append(cname) | |
| 235 self.comment = comment | |
| 236 self.trusted_signers = None | |
| 237 self.etag = None | |
| 238 self.streaming = False | |
| 239 | |
| 240 def __repr__(self): | |
| 241 return "DistributionSummary:%s" % self.domain_name | |
| 242 | |
| 243 def startElement(self, name, attrs, connection): | |
| 244 if name == 'TrustedSigners': | |
| 245 self.trusted_signers = TrustedSigners() | |
| 246 return self.trusted_signers | |
| 247 elif name == 'S3Origin': | |
| 248 self.origin = S3Origin() | |
| 249 return self.origin | |
| 250 elif name == 'CustomOrigin': | |
| 251 self.origin = CustomOrigin() | |
| 252 return self.origin | |
| 253 return None | |
| 254 | |
| 255 def endElement(self, name, value, connection): | |
| 256 if name == 'Id': | |
| 257 self.id = value | |
| 258 elif name == 'Status': | |
| 259 self.status = value | |
| 260 elif name == 'LastModifiedTime': | |
| 261 self.last_modified_time = value | |
| 262 elif name == 'DomainName': | |
| 263 self.domain_name = value | |
| 264 elif name == 'Origin': | |
| 265 self.origin = value | |
| 266 elif name == 'CNAME': | |
| 267 self.cnames.append(value) | |
| 268 elif name == 'Comment': | |
| 269 self.comment = value | |
| 270 elif name == 'Enabled': | |
| 271 if value.lower() == 'true': | |
| 272 self.enabled = True | |
| 273 else: | |
| 274 self.enabled = False | |
| 275 elif name == 'StreamingDistributionSummary': | |
| 276 self.streaming = True | |
| 277 else: | |
| 278 setattr(self, name, value) | |
| 279 | |
| 280 def get_distribution(self): | |
| 281 return self.connection.get_distribution_info(self.id) | |
| 282 | |
| 283 class StreamingDistributionSummary(DistributionSummary): | |
| 284 | |
| 285 def get_distribution(self): | |
| 286 return self.connection.get_streaming_distribution_info(self.id) | |
| 287 | |
| 288 class Distribution(object): | |
| 289 | |
| 290 def __init__(self, connection=None, config=None, domain_name='', | |
| 291 id='', last_modified_time=None, status=''): | |
| 292 self.connection = connection | |
| 293 self.config = config | |
| 294 self.domain_name = domain_name | |
| 295 self.id = id | |
| 296 self.last_modified_time = last_modified_time | |
| 297 self.status = status | |
| 298 self.in_progress_invalidation_batches = 0 | |
| 299 self.active_signers = None | |
| 300 self.etag = None | |
| 301 self._bucket = None | |
| 302 self._object_class = Object | |
| 303 | |
| 304 def __repr__(self): | |
| 305 return "Distribution:%s" % self.domain_name | |
| 306 | |
| 307 def startElement(self, name, attrs, connection): | |
| 308 if name == 'DistributionConfig': | |
| 309 self.config = DistributionConfig() | |
| 310 return self.config | |
| 311 elif name == 'ActiveTrustedSigners': | |
| 312 self.active_signers = ActiveTrustedSigners() | |
| 313 return self.active_signers | |
| 314 else: | |
| 315 return None | |
| 316 | |
| 317 def endElement(self, name, value, connection): | |
| 318 if name == 'Id': | |
| 319 self.id = value | |
| 320 elif name == 'LastModifiedTime': | |
| 321 self.last_modified_time = value | |
| 322 elif name == 'Status': | |
| 323 self.status = value | |
| 324 elif name == 'InProgressInvalidationBatches': | |
| 325 self.in_progress_invalidation_batches = int(value) | |
| 326 elif name == 'DomainName': | |
| 327 self.domain_name = value | |
| 328 else: | |
| 329 setattr(self, name, value) | |
| 330 | |
| 331 def update(self, enabled=None, cnames=None, comment=None): | |
| 332 """ | |
| 333 Update the configuration of the Distribution. The only values | |
| 334 of the DistributionConfig that can be directly updated are: | |
| 335 | |
| 336 * CNAMES | |
| 337 * Comment | |
| 338 * Whether the Distribution is enabled or not | |
| 339 | |
| 340 Any changes to the ``trusted_signers`` or ``origin`` properties of | |
| 341 this distribution's current config object will also be included in | |
| 342 the update. Therefore, to set the origin access identity for this | |
| 343 distribution, set ``Distribution.config.origin.origin_access_identity`` | |
| 344 before calling this update method. | |
| 345 | |
| 346 :type enabled: bool | |
| 347 :param enabled: Whether the Distribution is active or not. | |
| 348 | |
| 349 :type cnames: list of str | |
| 350 :param cnames: The DNS CNAME's associated with this | |
| 351 Distribution. Maximum of 10 values. | |
| 352 | |
| 353 :type comment: str or unicode | |
| 354 :param comment: The comment associated with the Distribution. | |
| 355 | |
| 356 """ | |
| 357 new_config = DistributionConfig(self.connection, self.config.origin, | |
| 358 self.config.enabled, self.config.caller_reference, | |
| 359 self.config.cnames, self.config.comment, | |
| 360 self.config.trusted_signers, | |
| 361 self.config.default_root_object) | |
| 362 if enabled is not None: | |
| 363 new_config.enabled = enabled | |
| 364 if cnames is not None: | |
| 365 new_config.cnames = cnames | |
| 366 if comment is not None: | |
| 367 new_config.comment = comment | |
| 368 self.etag = self.connection.set_distribution_config(self.id, self.etag, new_config) | |
| 369 self.config = new_config | |
| 370 self._object_class = Object | |
| 371 | |
| 372 def enable(self): | |
| 373 """ | |
| 374 Activate the Distribution. A convenience wrapper around | |
| 375 the update method. | |
| 376 """ | |
| 377 self.update(enabled=True) | |
| 378 | |
| 379 def disable(self): | |
| 380 """ | |
| 381 Deactivate the Distribution. A convenience wrapper around | |
| 382 the update method. | |
| 383 """ | |
| 384 self.update(enabled=False) | |
| 385 | |
| 386 def delete(self): | |
| 387 """ | |
| 388 Delete this CloudFront Distribution. The content | |
| 389 associated with the Distribution is not deleted from | |
| 390 the underlying Origin bucket in S3. | |
| 391 """ | |
| 392 self.connection.delete_distribution(self.id, self.etag) | |
| 393 | |
| 394 def _get_bucket(self): | |
| 395 if isinstance(self.config.origin, S3Origin): | |
| 396 if not self._bucket: | |
| 397 bucket_dns_name = self.config.origin.dns_name | |
| 398 bucket_name = bucket_dns_name.replace('.s3.amazonaws.com', '') | |
| 399 from boto.s3.connection import S3Connection | |
| 400 s3 = S3Connection(self.connection.aws_access_key_id, | |
| 401 self.connection.aws_secret_access_key, | |
| 402 proxy=self.connection.proxy, | |
| 403 proxy_port=self.connection.proxy_port, | |
| 404 proxy_user=self.connection.proxy_user, | |
| 405 proxy_pass=self.connection.proxy_pass) | |
| 406 self._bucket = s3.get_bucket(bucket_name) | |
| 407 self._bucket.distribution = self | |
| 408 self._bucket.set_key_class(self._object_class) | |
| 409 return self._bucket | |
| 410 else: | |
| 411 raise NotImplementedError('Unable to get_objects on CustomOrigin') | |
| 412 | |
| 413 def get_objects(self): | |
| 414 """ | |
| 415 Return a list of all content objects in this distribution. | |
| 416 | |
| 417 :rtype: list of :class:`boto.cloudfront.object.Object` | |
| 418 :return: The content objects | |
| 419 """ | |
| 420 bucket = self._get_bucket() | |
| 421 objs = [] | |
| 422 for key in bucket: | |
| 423 objs.append(key) | |
| 424 return objs | |
| 425 | |
| 426 def set_permissions(self, object, replace=False): | |
| 427 """ | |
| 428 Sets the S3 ACL grants for the given object to the appropriate | |
| 429 value based on the type of Distribution. If the Distribution | |
| 430 is serving private content the ACL will be set to include the | |
| 431 Origin Access Identity associated with the Distribution. If | |
| 432 the Distribution is serving public content the content will | |
| 433 be set up with "public-read". | |
| 434 | |
| 435 :type object: :class:`boto.cloudfront.object.Object` | |
| 436 :param enabled: The Object whose ACL is being set | |
| 437 | |
| 438 :type replace: bool | |
| 439 :param replace: If False, the Origin Access Identity will be | |
| 440 appended to the existing ACL for the object. | |
| 441 If True, the ACL for the object will be | |
| 442 completely replaced with one that grants | |
| 443 READ permission to the Origin Access Identity. | |
| 444 | |
| 445 """ | |
| 446 if isinstance(self.config.origin, S3Origin): | |
| 447 if self.config.origin.origin_access_identity: | |
| 448 id = self.config.origin.origin_access_identity.split('/')[-1] | |
| 449 oai = self.connection.get_origin_access_identity_info(id) | |
| 450 policy = object.get_acl() | |
| 451 if replace: | |
| 452 policy.acl = ACL() | |
| 453 policy.acl.add_user_grant('READ', oai.s3_user_id) | |
| 454 object.set_acl(policy) | |
| 455 else: | |
| 456 object.set_canned_acl('public-read') | |
| 457 | |
| 458 def set_permissions_all(self, replace=False): | |
| 459 """ | |
| 460 Sets the S3 ACL grants for all objects in the Distribution | |
| 461 to the appropriate value based on the type of Distribution. | |
| 462 | |
| 463 :type replace: bool | |
| 464 :param replace: If False, the Origin Access Identity will be | |
| 465 appended to the existing ACL for the object. | |
| 466 If True, the ACL for the object will be | |
| 467 completely replaced with one that grants | |
| 468 READ permission to the Origin Access Identity. | |
| 469 | |
| 470 """ | |
| 471 bucket = self._get_bucket() | |
| 472 for key in bucket: | |
| 473 self.set_permissions(key, replace) | |
| 474 | |
| 475 def add_object(self, name, content, headers=None, replace=True): | |
| 476 """ | |
| 477 Adds a new content object to the Distribution. The content | |
| 478 for the object will be copied to a new Key in the S3 Bucket | |
| 479 and the permissions will be set appropriately for the type | |
| 480 of Distribution. | |
| 481 | |
| 482 :type name: str or unicode | |
| 483 :param name: The name or key of the new object. | |
| 484 | |
| 485 :type content: file-like object | |
| 486 :param content: A file-like object that contains the content | |
| 487 for the new object. | |
| 488 | |
| 489 :type headers: dict | |
| 490 :param headers: A dictionary containing additional headers | |
| 491 you would like associated with the new | |
| 492 object in S3. | |
| 493 | |
| 494 :rtype: :class:`boto.cloudfront.object.Object` | |
| 495 :return: The newly created object. | |
| 496 """ | |
| 497 if self.config.origin.origin_access_identity: | |
| 498 policy = 'private' | |
| 499 else: | |
| 500 policy = 'public-read' | |
| 501 bucket = self._get_bucket() | |
| 502 object = bucket.new_key(name) | |
| 503 object.set_contents_from_file(content, headers=headers, policy=policy) | |
| 504 if self.config.origin.origin_access_identity: | |
| 505 self.set_permissions(object, replace) | |
| 506 return object | |
| 507 | |
| 508 def create_signed_url(self, url, keypair_id, | |
| 509 expire_time=None, valid_after_time=None, | |
| 510 ip_address=None, policy_url=None, | |
| 511 private_key_file=None, private_key_string=None): | |
| 512 """ | |
| 513 Creates a signed CloudFront URL that is only valid within the specified | |
| 514 parameters. | |
| 515 | |
| 516 :type url: str | |
| 517 :param url: The URL of the protected object. | |
| 518 | |
| 519 :type keypair_id: str | |
| 520 :param keypair_id: The keypair ID of the Amazon KeyPair used to sign | |
| 521 theURL. This ID MUST correspond to the private key | |
| 522 specified with private_key_file or private_key_string. | |
| 523 | |
| 524 :type expire_time: int | |
| 525 :param expire_time: The expiry time of the URL. If provided, the URL | |
| 526 will expire after the time has passed. If not provided the URL will | |
| 527 never expire. Format is a unix epoch. | |
| 528 Use int(time.time() + duration_in_sec). | |
| 529 | |
| 530 :type valid_after_time: int | |
| 531 :param valid_after_time: If provided, the URL will not be valid until | |
| 532 after valid_after_time. Format is a unix epoch. | |
| 533 Use int(time.time() + secs_until_valid). | |
| 534 | |
| 535 :type ip_address: str | |
| 536 :param ip_address: If provided, only allows access from the specified | |
| 537 IP address. Use '192.168.0.10' for a single IP or | |
| 538 use '192.168.0.0/24' CIDR notation for a subnet. | |
| 539 | |
| 540 :type policy_url: str | |
| 541 :param policy_url: If provided, allows the signature to contain | |
| 542 wildcard globs in the URL. For example, you could | |
| 543 provide: 'http://example.com/media/\*' and the policy | |
| 544 and signature would allow access to all contents of | |
| 545 the media subdirectory. If not specified, only | |
| 546 allow access to the exact url provided in 'url'. | |
| 547 | |
| 548 :type private_key_file: str or file object. | |
| 549 :param private_key_file: If provided, contains the filename of the | |
| 550 private key file used for signing or an open | |
| 551 file object containing the private key | |
| 552 contents. Only one of private_key_file or | |
| 553 private_key_string can be provided. | |
| 554 | |
| 555 :type private_key_string: str | |
| 556 :param private_key_string: If provided, contains the private key string | |
| 557 used for signing. Only one of private_key_file or | |
| 558 private_key_string can be provided. | |
| 559 | |
| 560 :rtype: str | |
| 561 :return: The signed URL. | |
| 562 """ | |
| 563 # Get the required parameters | |
| 564 params = self._create_signing_params( | |
| 565 url=url, keypair_id=keypair_id, expire_time=expire_time, | |
| 566 valid_after_time=valid_after_time, ip_address=ip_address, | |
| 567 policy_url=policy_url, private_key_file=private_key_file, | |
| 568 private_key_string=private_key_string) | |
| 569 | |
| 570 #combine these into a full url | |
| 571 if "?" in url: | |
| 572 sep = "&" | |
| 573 else: | |
| 574 sep = "?" | |
| 575 signed_url_params = [] | |
| 576 for key in ["Expires", "Policy", "Signature", "Key-Pair-Id"]: | |
| 577 if key in params: | |
| 578 param = "%s=%s" % (key, params[key]) | |
| 579 signed_url_params.append(param) | |
| 580 signed_url = url + sep + "&".join(signed_url_params) | |
| 581 return signed_url | |
| 582 | |
| 583 def _create_signing_params(self, url, keypair_id, | |
| 584 expire_time=None, valid_after_time=None, | |
| 585 ip_address=None, policy_url=None, | |
| 586 private_key_file=None, private_key_string=None): | |
| 587 """ | |
| 588 Creates the required URL parameters for a signed URL. | |
| 589 """ | |
| 590 params = {} | |
| 591 # Check if we can use a canned policy | |
| 592 if expire_time and not valid_after_time and not ip_address and not policy_url: | |
| 593 # we manually construct this policy string to ensure formatting | |
| 594 # matches signature | |
| 595 policy = self._canned_policy(url, expire_time) | |
| 596 params["Expires"] = str(expire_time) | |
| 597 else: | |
| 598 # If no policy_url is specified, default to the full url. | |
| 599 if policy_url is None: | |
| 600 policy_url = url | |
| 601 # Can't use canned policy | |
| 602 policy = self._custom_policy(policy_url, expires=expire_time, | |
| 603 valid_after=valid_after_time, | |
| 604 ip_address=ip_address) | |
| 605 | |
| 606 encoded_policy = self._url_base64_encode(policy) | |
| 607 params["Policy"] = encoded_policy | |
| 608 #sign the policy | |
| 609 signature = self._sign_string(policy, private_key_file, private_key_string) | |
| 610 #now base64 encode the signature (URL safe as well) | |
| 611 encoded_signature = self._url_base64_encode(signature) | |
| 612 params["Signature"] = encoded_signature | |
| 613 params["Key-Pair-Id"] = keypair_id | |
| 614 return params | |
| 615 | |
| 616 @staticmethod | |
| 617 def _canned_policy(resource, expires): | |
| 618 """ | |
| 619 Creates a canned policy string. | |
| 620 """ | |
| 621 policy = ('{"Statement":[{"Resource":"%(resource)s",' | |
| 622 '"Condition":{"DateLessThan":{"AWS:EpochTime":' | |
| 623 '%(expires)s}}}]}' % locals()) | |
| 624 return policy | |
| 625 | |
| 626 @staticmethod | |
| 627 def _custom_policy(resource, expires=None, valid_after=None, ip_address=None): | |
| 628 """ | |
| 629 Creates a custom policy string based on the supplied parameters. | |
| 630 """ | |
| 631 condition = {} | |
| 632 # SEE: http://docs.amazonwebservices.com/AmazonCloudFront/latest/DeveloperGuide/RestrictingAccessPrivateContent.html#CustomPolicy | |
| 633 # The 'DateLessThan' property is required. | |
| 634 if not expires: | |
| 635 # Defaults to ONE day | |
| 636 expires = int(time.time()) + 86400 | |
| 637 condition["DateLessThan"] = {"AWS:EpochTime": expires} | |
| 638 if valid_after: | |
| 639 condition["DateGreaterThan"] = {"AWS:EpochTime": valid_after} | |
| 640 if ip_address: | |
| 641 if '/' not in ip_address: | |
| 642 ip_address += "/32" | |
| 643 condition["IpAddress"] = {"AWS:SourceIp": ip_address} | |
| 644 policy = {"Statement": [{ | |
| 645 "Resource": resource, | |
| 646 "Condition": condition}]} | |
| 647 return json.dumps(policy, separators=(",", ":")) | |
| 648 | |
| 649 @staticmethod | |
| 650 def _sign_string(message, private_key_file=None, private_key_string=None): | |
| 651 """ | |
| 652 Signs a string for use with Amazon CloudFront. | |
| 653 Requires the rsa library be installed. | |
| 654 """ | |
| 655 try: | |
| 656 import rsa | |
| 657 except ImportError: | |
| 658 raise NotImplementedError("Boto depends on the python rsa " | |
| 659 "library to generate signed URLs for " | |
| 660 "CloudFront") | |
| 661 # Make sure only one of private_key_file and private_key_string is set | |
| 662 if private_key_file and private_key_string: | |
| 663 raise ValueError("Only specify the private_key_file or the private_key_string not both") | |
| 664 if not private_key_file and not private_key_string: | |
| 665 raise ValueError("You must specify one of private_key_file or private_key_string") | |
| 666 # If private_key_file is a file name, open it and read it | |
| 667 if private_key_string is None: | |
| 668 if isinstance(private_key_file, six.string_types): | |
| 669 with open(private_key_file, 'r') as file_handle: | |
| 670 private_key_string = file_handle.read() | |
| 671 # Otherwise, treat it like a file | |
| 672 else: | |
| 673 private_key_string = private_key_file.read() | |
| 674 | |
| 675 # Sign it! | |
| 676 private_key = rsa.PrivateKey.load_pkcs1(private_key_string) | |
| 677 signature = rsa.sign(str(message), private_key, 'SHA-1') | |
| 678 return signature | |
| 679 | |
| 680 @staticmethod | |
| 681 def _url_base64_encode(msg): | |
| 682 """ | |
| 683 Base64 encodes a string using the URL-safe characters specified by | |
| 684 Amazon. | |
| 685 """ | |
| 686 msg_base64 = base64.b64encode(msg) | |
| 687 msg_base64 = msg_base64.replace('+', '-') | |
| 688 msg_base64 = msg_base64.replace('=', '_') | |
| 689 msg_base64 = msg_base64.replace('/', '~') | |
| 690 return msg_base64 | |
| 691 | |
| 692 class StreamingDistribution(Distribution): | |
| 693 | |
| 694 def __init__(self, connection=None, config=None, domain_name='', | |
| 695 id='', last_modified_time=None, status=''): | |
| 696 super(StreamingDistribution, self).__init__(connection, config, | |
| 697 domain_name, id, last_modified_time, status) | |
| 698 self._object_class = StreamingObject | |
| 699 | |
| 700 def startElement(self, name, attrs, connection): | |
| 701 if name == 'StreamingDistributionConfig': | |
| 702 self.config = StreamingDistributionConfig() | |
| 703 return self.config | |
| 704 else: | |
| 705 return super(StreamingDistribution, self).startElement(name, attrs, | |
| 706 connection) | |
| 707 | |
| 708 def update(self, enabled=None, cnames=None, comment=None): | |
| 709 """ | |
| 710 Update the configuration of the StreamingDistribution. The only values | |
| 711 of the StreamingDistributionConfig that can be directly updated are: | |
| 712 | |
| 713 * CNAMES | |
| 714 * Comment | |
| 715 * Whether the Distribution is enabled or not | |
| 716 | |
| 717 Any changes to the ``trusted_signers`` or ``origin`` properties of | |
| 718 this distribution's current config object will also be included in | |
| 719 the update. Therefore, to set the origin access identity for this | |
| 720 distribution, set | |
| 721 ``StreamingDistribution.config.origin.origin_access_identity`` | |
| 722 before calling this update method. | |
| 723 | |
| 724 :type enabled: bool | |
| 725 :param enabled: Whether the StreamingDistribution is active or not. | |
| 726 | |
| 727 :type cnames: list of str | |
| 728 :param cnames: The DNS CNAME's associated with this | |
| 729 Distribution. Maximum of 10 values. | |
| 730 | |
| 731 :type comment: str or unicode | |
| 732 :param comment: The comment associated with the Distribution. | |
| 733 | |
| 734 """ | |
| 735 new_config = StreamingDistributionConfig(self.connection, | |
| 736 self.config.origin, | |
| 737 self.config.enabled, | |
| 738 self.config.caller_reference, | |
| 739 self.config.cnames, | |
| 740 self.config.comment, | |
| 741 self.config.trusted_signers) | |
| 742 if enabled is not None: | |
| 743 new_config.enabled = enabled | |
| 744 if cnames is not None: | |
| 745 new_config.cnames = cnames | |
| 746 if comment is not None: | |
| 747 new_config.comment = comment | |
| 748 self.etag = self.connection.set_streaming_distribution_config(self.id, | |
| 749 self.etag, | |
| 750 new_config) | |
| 751 self.config = new_config | |
| 752 self._object_class = StreamingObject | |
| 753 | |
| 754 def delete(self): | |
| 755 self.connection.delete_streaming_distribution(self.id, self.etag) | |
| 756 | |
| 757 |
