google-cloud-storage 1.17.0 → 1.18.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 99549b865b9a4222f87a79ca84f7bfb6f9291e48284fd27a8be32cd91a7fe6d5
4
- data.tar.gz: b4aee052f8aa4f56caa8c37ddd1d8cac51ad562f85f8bb1b14f9166bc7d65bd2
3
+ metadata.gz: 625405fb7ef792e7cd585f572f0e5102dfc6a03b4fb84daabce5fbe08b30fede
4
+ data.tar.gz: 93902f2000495402041a5a564956973b67e381326a68fe282bfdf6576b05d242
5
5
  SHA512:
6
- metadata.gz: 02ff1dab5acafa5b56ee161c1cae21fabd901f6e6edecd802d511c17732d8f837ac4e723babed90feaf2c54054880a36e908302b5ea64dc308e86107858fcc39
7
- data.tar.gz: d154a90c24ae673416bb3759d040bb9299cd3a6c53b53b3406a4fe663b525cda298b5fc69da041e828cb7503faf3f7f4380e878bfb3a01dc4b962f71e7ec2b1f
6
+ metadata.gz: 5e6eb7b4c4ec6c710d97c9611804c851b61a6878f5a10ef138c43f1aa0c8eff862232edfd6ae46e8f614ca4315c682b7486eff51fb3ace86f417cb1d49e92aa4
7
+ data.tar.gz: 10a8d0a3c177e4b0c9265e81929fa7090463da4eb3c577ee88b3456a66712bf85fef5b6cb34e212cdbf7d5080432ed5679de1829f69d548999b24b436c9e7a3d
@@ -1,5 +1,13 @@
1
1
  # Release History
2
2
 
3
+ ### 1.18.0 / 2019-04-09
4
+
5
+ * Add support for V4 signed URLs.
6
+ * Add version param to #signed_url.
7
+ * Fix file path encoding for V2 signed URLs.
8
+ * Change CGI encoding to URI (percent) encoding to fix URLs containing spaces in file path.
9
+ * Fix documentation typo.
10
+
3
11
  ### 1.17.0 / 2019-02-07
4
12
 
5
13
  * Add support for Bucket Policy Only with `Bucket#policy_only?`,
@@ -14,7 +22,7 @@
14
22
  This value was added in googleauth 0.7.0.
15
23
  * Loosen googleauth dependency
16
24
  Allow for new releases up to 0.10.
17
- The googleauth devs have committed to maintanining the current API
25
+ The googleauth devs have committed to maintaining the current API
18
26
  and will not make backwards compatible changes before 0.10.
19
27
 
20
28
  ### 1.15.0 / 2018-10-03
@@ -361,7 +361,7 @@ module Google
361
361
  # @param [Symbol, String] new_storage_class Storage class of the bucket.
362
362
  #
363
363
  def storage_class= new_storage_class
364
- @gapi.storage_class = storage_class_for(new_storage_class)
364
+ @gapi.storage_class = storage_class_for new_storage_class
365
365
  patch_gapi! :storage_class
366
366
  end
367
367
 
@@ -1353,14 +1353,12 @@ module Google
1353
1353
  alias combine compose
1354
1354
 
1355
1355
  ##
1356
- # Access without authentication can be granted to a File for a specified
1357
- # period of time. This URL uses a cryptographic signature of your
1358
- # credentials to access the file identified by `path`. A URL can be
1359
- # created for paths that do not yet exist. For instance, a URL can be
1360
- # created to `PUT` file contents to.
1356
+ # Generates a signed URL. See [Signed
1357
+ # URLs](https://cloud.google.com/storage/docs/access-control/signed-urls)
1358
+ # for more information.
1361
1359
  #
1362
- # Generating a URL requires service account credentials, either by
1363
- # connecting with a service account when calling
1360
+ # Generating a signed URL requires service account credentials, either
1361
+ # by connecting with a service account when calling
1364
1362
  # {Google::Cloud.storage}, or by passing in the service account `issuer`
1365
1363
  # and `signing_key` values. Although the private key can be passed as a
1366
1364
  # string for convenience, creating and storing an instance of
@@ -1372,20 +1370,26 @@ module Google
1372
1370
  # steps in [Service Account Authentication](
1373
1371
  # https://cloud.google.com/storage/docs/authentication#service_accounts).
1374
1372
  #
1375
- # @see https://cloud.google.com/storage/docs/access-control#Signed-URLs
1376
- # Access Control Signed URLs guide
1373
+ # @see https://cloud.google.com/storage/docs/access-control/signed-urls
1374
+ # Signed URLs guide
1375
+ # @see https://cloud.google.com/storage/docs/access-control/signed-urls#signing-resumable
1376
+ # Using signed URLs with resumable uploads
1377
1377
  #
1378
- # @param [String] path Path to the file in Google Cloud Storage.
1378
+ # @param [String, nil] path Path to the file in Google Cloud Storage, or
1379
+ # `nil` to generate a URL for listing all files in the bucket.
1379
1380
  # @param [String] method The HTTP verb to be used with the signed URL.
1380
1381
  # Signed URLs can be used
1381
1382
  # with `GET`, `HEAD`, `PUT`, and `DELETE` requests. Default is `GET`.
1382
1383
  # @param [Integer] expires The number of seconds until the URL expires.
1383
- # Default is 300/5 minutes.
1384
+ # If the `version` is `:v2`, the default is 300 (5 minutes). If the
1385
+ # `version` is `:v4`, the default is 604800 (7 days).
1384
1386
  # @param [String] content_type When provided, the client (browser) must
1385
- # send this value in the HTTP header. e.g. `text/plain`
1387
+ # send this value in the HTTP header. e.g. `text/plain`. This param is
1388
+ # not used if the `version` is `:v4`.
1386
1389
  # @param [String] content_md5 The MD5 digest value in base64. If you
1387
1390
  # provide this in the string, the client (usually a browser) must
1388
- # provide this HTTP header with this same value in its request.
1391
+ # provide this HTTP header with this same value in its request. This
1392
+ # param is not used if the `version` is `:v4`.
1389
1393
  # @param [Hash] headers Google extension headers (custom HTTP headers
1390
1394
  # that begin with `x-goog-`) that must be included in requests that
1391
1395
  # use the signed URL.
@@ -1403,6 +1407,9 @@ module Google
1403
1407
  # using the URL, but only when the file resource is missing the
1404
1408
  # corresponding values. (These values can be permanently set using
1405
1409
  # {File#content_disposition=} and {File#content_type=}.)
1410
+ # @param [Symbol, String] version The version of the signed credential
1411
+ # to create. Must be one of ':v2' or ':v4'. The default value is
1412
+ # ':v2'.
1406
1413
  #
1407
1414
  # @return [String]
1408
1415
  #
@@ -1414,21 +1421,20 @@ module Google
1414
1421
  # bucket = storage.bucket "my-todo-app"
1415
1422
  # shared_url = bucket.signed_url "avatars/heidi/400x400.png"
1416
1423
  #
1417
- # @example Any of the option parameters may be specified:
1424
+ # @example Using the `expires` and `version` options:
1418
1425
  # require "google/cloud/storage"
1419
1426
  #
1420
1427
  # storage = Google::Cloud::Storage.new
1421
1428
  #
1422
1429
  # bucket = storage.bucket "my-todo-app"
1423
1430
  # shared_url = bucket.signed_url "avatars/heidi/400x400.png",
1424
- # method: "PUT",
1425
- # content_type: "image/png",
1426
- # expires: 300 # 5 minutes from now
1431
+ # expires: 300, # 5 minutes from now
1432
+ # version: :v4
1427
1433
  #
1428
- # @example Using the issuer and signing_key options:
1434
+ # @example Using the `issuer` and `signing_key` options:
1429
1435
  # require "google/cloud/storage"
1430
1436
  #
1431
- # storage = Google::Cloud.storage
1437
+ # storage = Google::Cloud::Storage.new
1432
1438
  #
1433
1439
  # bucket = storage.bucket "my-todo-app"
1434
1440
  # key = OpenSSL::PKey::RSA.new "-----BEGIN PRIVATE KEY-----\n..."
@@ -1436,10 +1442,10 @@ module Google
1436
1442
  # issuer: "service-account@gcloud.com",
1437
1443
  # signing_key: key
1438
1444
  #
1439
- # @example Using the headers option:
1445
+ # @example Using the `headers` option:
1440
1446
  # require "google/cloud/storage"
1441
1447
  #
1442
- # storage = Google::Cloud.storage
1448
+ # storage = Google::Cloud::Storage.new
1443
1449
  #
1444
1450
  # bucket = storage.bucket "my-todo-app"
1445
1451
  # shared_url = bucket.signed_url "avatars/heidi/400x400.png",
@@ -1448,18 +1454,54 @@ module Google
1448
1454
  # "x-goog-meta-foo" => "bar,baz"
1449
1455
  # }
1450
1456
  #
1451
- def signed_url path, method: nil, expires: nil, content_type: nil,
1457
+ # @example Generating a signed URL for resumable upload:
1458
+ # require "google/cloud/storage"
1459
+ #
1460
+ # storage = Google::Cloud::Storage.new
1461
+ #
1462
+ # bucket = storage.bucket "my-todo-app"
1463
+ # url = bucket.signed_url "avatars/heidi/400x400.png",
1464
+ # method: "POST",
1465
+ # content_type: "image/png",
1466
+ # headers: {
1467
+ # "x-goog-resumable" => "start"
1468
+ # }
1469
+ # # Send the `x-goog-resumable:start` header and the content type
1470
+ # # with the resumable upload POST request.
1471
+ #
1472
+ # @example Omitting `path` for a URL to list all files in the bucket.
1473
+ # require "google/cloud/storage"
1474
+ #
1475
+ # storage = Google::Cloud::Storage.new
1476
+ #
1477
+ # bucket = storage.bucket "my-todo-app"
1478
+ # list_files_url = bucket.signed_url version: :v4
1479
+ #
1480
+ def signed_url path = nil, method: nil, expires: nil, content_type: nil,
1452
1481
  content_md5: nil, headers: nil, issuer: nil,
1453
1482
  client_email: nil, signing_key: nil, private_key: nil,
1454
- query: nil
1483
+ query: nil, version: nil
1455
1484
  ensure_service!
1456
- signer = File::Signer.from_bucket self, path
1457
- signer.signed_url method: method, expires: expires, headers: headers,
1458
- content_type: content_type,
1459
- content_md5: content_md5, issuer: issuer,
1460
- client_email: client_email,
1461
- signing_key: signing_key, private_key: private_key,
1462
- query: query
1485
+ version ||= :v2
1486
+ case version.to_sym
1487
+ when :v2
1488
+ signer = File::SignerV2.from_bucket self, path
1489
+ signer.signed_url method: method, expires: expires,
1490
+ headers: headers, content_type: content_type,
1491
+ content_md5: content_md5, issuer: issuer,
1492
+ client_email: client_email,
1493
+ signing_key: signing_key,
1494
+ private_key: private_key, query: query
1495
+ when :v4
1496
+ signer = File::SignerV4.from_bucket self, path
1497
+ signer.signed_url method: method, expires: expires,
1498
+ headers: headers, issuer: issuer,
1499
+ client_email: client_email,
1500
+ signing_key: signing_key,
1501
+ private_key: private_key, query: query
1502
+ else
1503
+ raise ArgumentError, "version '#{version}' not supported"
1504
+ end
1463
1505
  end
1464
1506
 
1465
1507
  ##
@@ -1563,7 +1605,7 @@ module Google
1563
1605
  private_key: nil
1564
1606
  ensure_service!
1565
1607
 
1566
- signer = File::Signer.from_bucket self, path
1608
+ signer = File::SignerV2.from_bucket self, path
1567
1609
  signer.post_object issuer: issuer, client_email: client_email,
1568
1610
  signing_key: signing_key, private_key: private_key,
1569
1611
  policy: policy
@@ -128,7 +128,7 @@ module Google
128
128
  def all request_limit: nil
129
129
  request_limit = request_limit.to_i if request_limit
130
130
  unless block_given?
131
- return enum_for(:all, request_limit: request_limit)
131
+ return enum_for :all, request_limit: request_limit
132
132
  end
133
133
  results = self
134
134
  loop do
@@ -17,7 +17,8 @@ require "google/cloud/storage/convert"
17
17
  require "google/cloud/storage/file/acl"
18
18
  require "google/cloud/storage/file/list"
19
19
  require "google/cloud/storage/file/verifier"
20
- require "google/cloud/storage/file/signer"
20
+ require "google/cloud/storage/file/signer_v2"
21
+ require "google/cloud/storage/file/signer_v4"
21
22
  require "zlib"
22
23
 
23
24
  module Google
@@ -443,7 +444,7 @@ module Google
443
444
  # @param [Symbol, String] storage_class Storage class of the file.
444
445
  #
445
446
  def storage_class= storage_class
446
- @gapi.storage_class = storage_class_for(storage_class)
447
+ @gapi.storage_class = storage_class_for storage_class
447
448
  update_gapi! :storage_class
448
449
  end
449
450
 
@@ -940,7 +941,7 @@ module Google
940
941
  key: encryption_key, range: range,
941
942
  user_project: user_project
942
943
  # FIX: downloading with encryption key will return nil
943
- file ||= ::File.new(path)
944
+ file ||= ::File.new path
944
945
  verify = :none if range
945
946
  verify_file! file, verify
946
947
  if !skip_decompress &&
@@ -1384,12 +1385,12 @@ module Google
1384
1385
  alias url public_url
1385
1386
 
1386
1387
  ##
1387
- # Access without authentication can be granted to a File for a specified
1388
- # period of time. This URL uses a cryptographic signature of your
1389
- # credentials to access the file.
1388
+ # Generates a signed URL for the file. See [Signed
1389
+ # URLs](https://cloud.google.com/storage/docs/access-control/signed-urls)
1390
+ # for more information.
1390
1391
  #
1391
- # Generating a URL requires service account credentials, either by
1392
- # connecting with a service account when calling
1392
+ # Generating a signed URL requires service account credentials, either
1393
+ # by connecting with a service account when calling
1393
1394
  # {Google::Cloud.storage}, or by passing in the service account `issuer`
1394
1395
  # and `signing_key` values. Although the private key can be passed as a
1395
1396
  # string for convenience, creating and storing an instance of
@@ -1401,19 +1402,24 @@ module Google
1401
1402
  # steps in [Service Account Authentication](
1402
1403
  # https://cloud.google.com/storage/docs/authentication#service_accounts).
1403
1404
  #
1404
- # @see https://cloud.google.com/storage/docs/access-control#Signed-URLs
1405
- # Access Control Signed URLs guide
1405
+ # @see https://cloud.google.com/storage/docs/access-control/signed-urls
1406
+ # Signed URLs guide
1407
+ # @see https://cloud.google.com/storage/docs/access-control/signed-urls#signing-resumable
1408
+ # Using signed URLs with resumable uploads
1406
1409
  #
1407
1410
  # @param [String] method The HTTP verb to be used with the signed URL.
1408
1411
  # Signed URLs can be used
1409
1412
  # with `GET`, `HEAD`, `PUT`, and `DELETE` requests. Default is `GET`.
1410
1413
  # @param [Integer] expires The number of seconds until the URL expires.
1411
- # Default is 300/5 minutes.
1414
+ # If the `version` is `:v2`, the default is 300 (5 minutes). If the
1415
+ # `version` is `:v4`, the default is 604800 (7 days).
1412
1416
  # @param [String] content_type When provided, the client (browser) must
1413
- # send this value in the HTTP header. e.g. `text/plain`
1417
+ # send this value in the HTTP header. e.g. `text/plain`. This param is
1418
+ # not used if the `version` is `:v4`.
1414
1419
  # @param [String] content_md5 The MD5 digest value in base64. If you
1415
1420
  # provide this in the string, the client (usually a browser) must
1416
- # provide this HTTP header with this same value in its request.
1421
+ # provide this HTTP header with this same value in its request. This
1422
+ # param is not used if the `version` is `:v4`.
1417
1423
  # @param [Hash] headers Google extension headers (custom HTTP headers
1418
1424
  # that begin with `x-goog-`) that must be included in requests that
1419
1425
  # use the signed URL.
@@ -1431,6 +1437,9 @@ module Google
1431
1437
  # using the URL, but only when the file resource is missing the
1432
1438
  # corresponding values. (These values can be permanently set using
1433
1439
  # {#content_disposition=} and {#content_type=}.)
1440
+ # @param [Symbol, String] version The version of the signed credential
1441
+ # to create. Must be one of ':v2' or ':v4'. The default value is
1442
+ # ':v2'.
1434
1443
  #
1435
1444
  # @return [String]
1436
1445
  #
@@ -1443,21 +1452,20 @@ module Google
1443
1452
  # file = bucket.file "avatars/heidi/400x400.png"
1444
1453
  # shared_url = file.signed_url
1445
1454
  #
1446
- # @example Any of the option parameters may be specified:
1455
+ # @example Using the `expires` and `version` options:
1447
1456
  # require "google/cloud/storage"
1448
1457
  #
1449
1458
  # storage = Google::Cloud::Storage.new
1450
1459
  #
1451
1460
  # bucket = storage.bucket "my-todo-app"
1452
1461
  # file = bucket.file "avatars/heidi/400x400.png"
1453
- # shared_url = file.signed_url method: "PUT",
1454
- # content_type: "image/png",
1455
- # expires: 300 # 5 minutes from now
1456
- #
1462
+ # shared_url = file.signed_url expires: 300, # 5 minutes from now
1463
+ # version: :v4
1464
+
1457
1465
  # @example Using the `issuer` and `signing_key` options:
1458
1466
  # require "google/cloud/storage"
1459
1467
  #
1460
- # storage = Google::Cloud.storage
1468
+ # storage = Google::Cloud::Storage.new
1461
1469
  #
1462
1470
  # bucket = storage.bucket "my-todo-app"
1463
1471
  # file = bucket.file "avatars/heidi/400x400.png"
@@ -1478,18 +1486,46 @@ module Google
1478
1486
  # "x-goog-meta-foo" => "bar,baz"
1479
1487
  # }
1480
1488
  #
1489
+ # @example Generating a signed URL for resumable upload:
1490
+ # require "google/cloud/storage"
1491
+ #
1492
+ # storage = Google::Cloud::Storage.new
1493
+ #
1494
+ # bucket = storage.bucket "my-todo-app"
1495
+ # file = bucket.file "avatars/heidi/400x400.png", skip_lookup: true
1496
+ # url = file.signed_url method: "POST",
1497
+ # content_type: "image/png",
1498
+ # headers: {
1499
+ # "x-goog-resumable" => "start"
1500
+ # }
1501
+ # # Send the `x-goog-resumable:start` header and the content type
1502
+ # # with the resumable upload POST request.
1503
+ #
1481
1504
  def signed_url method: nil, expires: nil, content_type: nil,
1482
1505
  content_md5: nil, headers: nil, issuer: nil,
1483
1506
  client_email: nil, signing_key: nil, private_key: nil,
1484
- query: nil
1507
+ query: nil, version: nil
1485
1508
  ensure_service!
1486
- signer = File::Signer.from_file self
1487
- signer.signed_url method: method, expires: expires, headers: headers,
1488
- content_type: content_type,
1489
- content_md5: content_md5,
1490
- issuer: issuer, client_email: client_email,
1491
- signing_key: signing_key, private_key: private_key,
1492
- query: query
1509
+ version ||= :v2
1510
+ case version.to_sym
1511
+ when :v2
1512
+ signer = File::SignerV2.from_file self
1513
+ signer.signed_url method: method, expires: expires,
1514
+ headers: headers, content_type: content_type,
1515
+ content_md5: content_md5, issuer: issuer,
1516
+ client_email: client_email,
1517
+ signing_key: signing_key,
1518
+ private_key: private_key, query: query
1519
+ when :v4
1520
+ signer = File::SignerV4.from_file self
1521
+ signer.signed_url method: method, expires: expires,
1522
+ headers: headers, issuer: issuer,
1523
+ client_email: client_email,
1524
+ signing_key: signing_key,
1525
+ private_key: private_key, query: query
1526
+ else
1527
+ raise ArgumentError, "version '#{version}' not supported"
1528
+ end
1493
1529
  end
1494
1530
 
1495
1531
  ##
@@ -1737,11 +1773,11 @@ module Google
1737
1773
  # system or a StringIO instance.
1738
1774
  def gzip_decompress local_file
1739
1775
  if local_file.respond_to? :path
1740
- gz = ::File.open(Pathname(local_file).to_path, "rb") do |f|
1741
- Zlib::GzipReader.new(StringIO.new(f.read))
1776
+ gz = ::File.open Pathname(local_file).to_path, "rb" do |f|
1777
+ Zlib::GzipReader.new StringIO.new(f.read)
1742
1778
  end
1743
1779
  uncompressed_string = gz.read
1744
- ::File.open(Pathname(local_file).to_path, "w") do |f|
1780
+ ::File.open Pathname(local_file).to_path, "w" do |f|
1745
1781
  f.write uncompressed_string
1746
1782
  f
1747
1783
  end
@@ -142,7 +142,7 @@ module Google
142
142
  def all request_limit: nil
143
143
  request_limit = request_limit.to_i if request_limit
144
144
  unless block_given?
145
- return enum_for(:all, request_limit: request_limit)
145
+ return enum_for :all, request_limit: request_limit
146
146
  end
147
147
  results = self
148
148
  loop do
@@ -13,6 +13,7 @@
13
13
  # limitations under the License.
14
14
 
15
15
 
16
+ require "addressable/uri"
16
17
  require "base64"
17
18
  require "cgi"
18
19
  require "openssl"
@@ -24,7 +25,7 @@ module Google
24
25
  class File
25
26
  ##
26
27
  # @private Create a signed_url for a file.
27
- class Signer
28
+ class SignerV2
28
29
  def initialize bucket, path, service
29
30
  @bucket = bucket
30
31
  @path = path
@@ -42,10 +43,7 @@ module Google
42
43
  ##
43
44
  # The external path to the file.
44
45
  def ext_path
45
- escaped_path = String(@path).split("/").map do |node|
46
- CGI.escape node
47
- end.join("/")
48
- "/#{CGI.escape @bucket}/#{escaped_path}"
46
+ Addressable::URI.escape "/#{@bucket}/#{@path}"
49
47
  end
50
48
 
51
49
  ##
@@ -93,7 +91,7 @@ module Google
93
91
  raise SignedUrlUnavailable unless i && s
94
92
 
95
93
  policy_str = p.to_json
96
- policy = Base64.strict_encode64(policy_str).delete("\n")
94
+ policy = Base64.strict_encode64(policy_str).delete "\n"
97
95
 
98
96
  signature = generate_signature s, policy
99
97
 
@@ -121,7 +119,7 @@ module Google
121
119
  signing_key = OpenSSL::PKey::RSA.new signing_key
122
120
  end
123
121
  signature = signing_key.sign OpenSSL::Digest::SHA256.new, secret
124
- Base64.strict_encode64(signature).delete("\n")
122
+ Base64.strict_encode64(signature).delete "\n"
125
123
  end
126
124
 
127
125
  def generate_signed_url issuer, signed_string, expires, query
@@ -0,0 +1,182 @@
1
+ # Copyright 2019 Google LLC
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
+ # https://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
+
16
+ require "addressable/uri"
17
+ require "cgi"
18
+ require "openssl"
19
+ require "google/cloud/storage/errors"
20
+
21
+ module Google
22
+ module Cloud
23
+ module Storage
24
+ class File
25
+ ##
26
+ # @private Create a signed_url for a file.
27
+ class SignerV4
28
+ def initialize bucket_name, file_name, service
29
+ @bucket_name = bucket_name
30
+ @file_name = file_name
31
+ @service = service
32
+ end
33
+
34
+ def self.from_file file
35
+ new file.bucket, file.name, file.service
36
+ end
37
+
38
+ def self.from_bucket bucket, file_name
39
+ new bucket.name, file_name, bucket.service
40
+ end
41
+
42
+ def signed_url method: "GET", expires: nil, headers: nil,
43
+ issuer: nil, client_email: nil, signing_key: nil,
44
+ private_key: nil, query: nil
45
+ issuer, signer = issuer_and_signer issuer, client_email,
46
+ signing_key, private_key
47
+ datetime_now = Time.now.utc
48
+ goog_date = datetime_now.strftime "%Y%m%dT%H%M%SZ"
49
+ datestamp = datetime_now.strftime "%Y%m%d"
50
+ # goog4_request is not checked.
51
+ scope = "#{datestamp}/auto/storage/goog4_request"
52
+
53
+ canonical_headers_str, signed_headers_str = \
54
+ canonical_and_signed_headers headers
55
+
56
+ algorithm = "GOOG4-RSA-SHA256"
57
+ expires = determine_expires expires
58
+ credential = CGI.escape issuer + "/" + scope
59
+ canonical_query_str = canonical_query query, algorithm,
60
+ credential, goog_date,
61
+ expires, signed_headers_str
62
+
63
+ # From AWS: You don't include a payload hash in the Canonical
64
+ # Request, because when you create a presigned URL, you don't know
65
+ # the payload content because the URL is used to upload an arbitrary
66
+ # payload. Instead, you use a constant string UNSIGNED-PAYLOAD.
67
+ canonical_request = [method,
68
+ ext_path,
69
+ canonical_query_str,
70
+ canonical_headers_str,
71
+ signed_headers_str,
72
+ "UNSIGNED-PAYLOAD"].join("\n")
73
+ # Construct string to sign
74
+ req_sha = Digest::SHA256.hexdigest canonical_request
75
+ string_to_sign = [algorithm, goog_date, scope, req_sha].join "\n"
76
+
77
+ # Sign string
78
+ signature = signer.call string_to_sign
79
+
80
+ # Construct signed URL
81
+ "#{ext_url}?#{canonical_query_str}&X-Goog-Signature=#{signature}"
82
+ end
83
+
84
+ protected
85
+
86
+ def determine_issuer issuer, client_email
87
+ # Parse the Service Account and get client id and private key
88
+ issuer = issuer || client_email || @service.credentials.issuer
89
+ unless issuer
90
+ raise SignedUrlUnavailable, "issuer (client_email) missing"
91
+ end
92
+ issuer
93
+ end
94
+
95
+ def determine_signing_key signing_key, private_key
96
+ signing_key = signing_key || private_key ||
97
+ @service.credentials.signing_key
98
+ unless signing_key
99
+ raise SignedUrlUnavailable, "signing_key (private_key) missing"
100
+ end
101
+ signing_key
102
+ end
103
+
104
+ def service_account_signer signer
105
+ unless signer.respond_to? :sign
106
+ signer = OpenSSL::PKey::RSA.new signer
107
+ end
108
+ # Sign string to sign
109
+ lambda do |string_to_sign|
110
+ sig = signer.sign OpenSSL::Digest::SHA256.new, string_to_sign
111
+ sig.unpack("H*").first
112
+ end
113
+ end
114
+
115
+ def issuer_and_signer issuer, client_email, signing_key, private_key
116
+ issuer = determine_issuer issuer, client_email
117
+ signing_key = determine_signing_key signing_key, private_key
118
+ signer = service_account_signer signing_key
119
+ [issuer, signer]
120
+ end
121
+
122
+ def canonical_and_signed_headers headers
123
+ # Headers needs to be in alpha order.
124
+ canonical_headers = headers || {}
125
+ canonical_headers = Hash[canonical_headers.map do |k, v|
126
+ [k.downcase, v.strip.gsub(/\s+/, " ")]
127
+ end]
128
+ canonical_headers["host"] = "storage.googleapis.com"
129
+
130
+ canonical_headers = canonical_headers.sort_by do |k, _|
131
+ k.downcase
132
+ end.to_h
133
+ canonical_headers_str = ""
134
+ canonical_headers.each do |k, v|
135
+ canonical_headers_str += "#{k}:#{v}\n"
136
+ end
137
+ signed_headers_str = ""
138
+ canonical_headers.each_key { |k| signed_headers_str += "#{k};" }
139
+ signed_headers_str = signed_headers_str.chomp ";"
140
+ [canonical_headers_str, signed_headers_str]
141
+ end
142
+
143
+ def determine_expires expires
144
+ expires ||= 604_800 # Default is 7 days.
145
+ if expires > 604_800
146
+ raise ArgumentError, "Expiration time can't be longer than a week"
147
+ end
148
+ expires
149
+ end
150
+
151
+ def canonical_query query, algorithm, credential, goog_date, expires,
152
+ signed_headers_str
153
+ query ||= {}
154
+ query["X-Goog-Algorithm"] = algorithm
155
+ query["X-Goog-Credential"] = credential
156
+ query["X-Goog-Date"] = goog_date
157
+ query["X-Goog-Expires"] = expires
158
+ query["X-Goog-SignedHeaders"] = CGI.escape signed_headers_str
159
+ query = query.sort_by { |k, _| k.to_s.downcase }.to_h
160
+ canonical_query_str = ""
161
+ query.each { |k, v| canonical_query_str += "#{k}=#{v}&" }
162
+ canonical_query_str.chomp "&"
163
+ end
164
+
165
+ ##
166
+ # The URI-encoded (percent encoded) external path to the file.
167
+ def ext_path
168
+ path = "/#{@bucket_name}"
169
+ path += "/#{String(@file_name)}" if @file_name && !@file_name.empty?
170
+ Addressable::URI.escape path
171
+ end
172
+
173
+ ##
174
+ # The external url to the file.
175
+ def ext_url
176
+ "#{GOOGLEAPIS_URL}#{ext_path}"
177
+ end
178
+ end
179
+ end
180
+ end
181
+ end
182
+ end
@@ -51,7 +51,7 @@ module Google
51
51
 
52
52
  def self.md5_for local_file
53
53
  if local_file.respond_to? :path
54
- ::File.open(Pathname(local_file).to_path, "rb") do |f|
54
+ ::File.open Pathname(local_file).to_path, "rb" do |f|
55
55
  ::Digest::MD5.file(f).base64digest
56
56
  end
57
57
  else # StringIO
@@ -64,7 +64,7 @@ module Google
64
64
 
65
65
  def self.crc32c_for local_file
66
66
  if local_file.respond_to? :path
67
- ::File.open(Pathname(local_file).to_path, "rb") do |f|
67
+ ::File.open Pathname(local_file).to_path, "rb" do |f|
68
68
  ::Digest::CRC32c.file(f).base64digest
69
69
  end
70
70
  else # StringIO
@@ -358,7 +358,7 @@ module Google
358
358
  name: bucket_name,
359
359
  location: location
360
360
  }.delete_if { |_, v| v.nil? })
361
- storage_class = storage_class_for(storage_class)
361
+ storage_class = storage_class_for storage_class
362
362
  updater = Bucket::Updater.new(new_bucket).tap do |b|
363
363
  b.logging_bucket = logging_bucket unless logging_bucket.nil?
364
364
  b.logging_prefix = logging_prefix unless logging_prefix.nil?
@@ -379,11 +379,9 @@ module Google
379
379
  end
380
380
 
381
381
  ##
382
- # Access without authentication can be granted to a File for a specified
383
- # period of time. This URL uses a cryptographic signature of your
384
- # credentials to access the file identified by `path`. A URL can be
385
- # created for paths that do not yet exist. For instance, a URL can be
386
- # created to `PUT` file contents to.
382
+ # Generates a signed URL. See [Signed
383
+ # URLs](https://cloud.google.com/storage/docs/access-control/signed-urls)
384
+ # for more information.
387
385
  #
388
386
  # Generating a URL requires service account credentials, either by
389
387
  # connecting with a service account when calling
@@ -398,21 +396,27 @@ module Google
398
396
  # steps in [Service Account Authentication](
399
397
  # https://cloud.google.com/storage/docs/authentication#service_accounts).
400
398
  #
401
- # @see https://cloud.google.com/storage/docs/access-control#Signed-URLs
402
- # Access Control Signed URLs guide
399
+ # @see https://cloud.google.com/storage/docs/access-control/signed-urls
400
+ # Signed URLs guide
401
+ # @see https://cloud.google.com/storage/docs/access-control/signed-urls#signing-resumable
402
+ # Using signed URLs with resumable uploads
403
403
  #
404
- # @param [String] bucket Name of the bucket.
404
+ # @param [String, nil] bucket Name of the bucket, or nil if URL for all
405
+ # buckets is desired.
405
406
  # @param [String] path Path to the file in Google Cloud Storage.
406
407
  # @param [String] method The HTTP verb to be used with the signed URL.
407
408
  # Signed URLs can be used
408
409
  # with `GET`, `HEAD`, `PUT`, and `DELETE` requests. Default is `GET`.
409
410
  # @param [Integer] expires The number of seconds until the URL expires.
410
- # Default is 300/5 minutes.
411
+ # If the `version` is `:v2`, the default is 300 (5 minutes). If the
412
+ # `version` is `:v4`, the default is 604800 (7 days).
411
413
  # @param [String] content_type When provided, the client (browser) must
412
- # send this value in the HTTP header. e.g. `text/plain`
414
+ # send this value in the HTTP header. e.g. `text/plain`. This param is
415
+ # not used if the `version` is `:v4`.
413
416
  # @param [String] content_md5 The MD5 digest value in base64. If you
414
417
  # provide this in the string, the client (usually a browser) must
415
- # provide this HTTP header with this same value in its request.
418
+ # provide this HTTP header with this same value in its request. This
419
+ # param is not used if the `version` is `:v4`.
416
420
  # @param [Hash] headers Google extension headers (custom HTTP headers
417
421
  # that begin with `x-goog-`) that must be included in requests that
418
422
  # use the signed URL.
@@ -430,6 +434,9 @@ module Google
430
434
  # using the URL, but only when the file resource is missing the
431
435
  # corresponding values. (These values can be permanently set using
432
436
  # {File#content_disposition=} and {File#content_type=}.)
437
+ # @param [Symbol, String] version The version of the signed credential
438
+ # to create. Must be one of ':v2' or ':v4'. The default value is
439
+ # ':v2'.
433
440
  #
434
441
  # @return [String]
435
442
  #
@@ -442,7 +449,7 @@ module Google
442
449
  # file_path = "avatars/heidi/400x400.png"
443
450
  # shared_url = storage.signed_url bucket_name, file_path
444
451
  #
445
- # @example Any of the option parameters may be specified:
452
+ # @example Using the `expires` and `version` options:
446
453
  # require "google/cloud/storage"
447
454
  #
448
455
  # storage = Google::Cloud::Storage.new
@@ -450,14 +457,13 @@ module Google
450
457
  # bucket_name = "my-todo-app"
451
458
  # file_path = "avatars/heidi/400x400.png"
452
459
  # shared_url = storage.signed_url bucket_name, file_path,
453
- # method: "PUT",
454
- # content_type: "image/png",
455
- # expires: 300 # 5 minutes from now
460
+ # expires: 300, # 5 minutes from now
461
+ # version: :v4
456
462
  #
457
- # @example Using the issuer and signing_key options:
463
+ # @example Using the `issuer` and `signing_key` options:
458
464
  # require "google/cloud/storage"
459
465
  #
460
- # storage = Google::Cloud.storage
466
+ # storage = Google::Cloud::Storage.new
461
467
  #
462
468
  # bucket_name = "my-todo-app"
463
469
  # file_path = "avatars/heidi/400x400.png"
@@ -467,10 +473,10 @@ module Google
467
473
  # issuer: issuer_email,
468
474
  # signing_key: key
469
475
  #
470
- # @example Using the headers option:
476
+ # @example Using the `headers` option:
471
477
  # require "google/cloud/storage"
472
478
  #
473
- # storage = Google::Cloud.storage
479
+ # storage = Google::Cloud::Storage.new
474
480
  #
475
481
  # bucket_name = "my-todo-app"
476
482
  # file_path = "avatars/heidi/400x400.png"
@@ -480,17 +486,48 @@ module Google
480
486
  # "x-goog-meta-foo" => "bar,baz"
481
487
  # }
482
488
  #
489
+ # @example Generating a signed URL for resumable upload:
490
+ # require "google/cloud/storage"
491
+ #
492
+ # storage = Google::Cloud::Storage.new
493
+ #
494
+ # bucket_name = "my-todo-app"
495
+ # file_path = "avatars/heidi/400x400.png"
496
+ #
497
+ # url = storage.signed_url bucket_name, file_path,
498
+ # method: "POST",
499
+ # content_type: "image/png",
500
+ # headers: {
501
+ # "x-goog-resumable" => "start"
502
+ # }
503
+ # # Send the `x-goog-resumable:start` header and the content type
504
+ # # with the resumable upload POST request.
505
+ #
483
506
  def signed_url bucket, path, method: nil, expires: nil,
484
507
  content_type: nil, content_md5: nil, headers: nil,
485
508
  issuer: nil, client_email: nil, signing_key: nil,
486
- private_key: nil, query: nil
487
- signer = File::Signer.new bucket, path, service
488
- signer.signed_url method: method, expires: expires, headers: headers,
489
- content_type: content_type,
490
- content_md5: content_md5,
491
- issuer: issuer, client_email: client_email,
492
- signing_key: signing_key, private_key: private_key,
493
- query: query
509
+ private_key: nil, query: nil, version: nil
510
+ version ||= :v2
511
+ case version.to_sym
512
+ when :v2
513
+ signer = File::SignerV2.new bucket, path, service
514
+
515
+ signer.signed_url method: method, expires: expires,
516
+ headers: headers, content_type: content_type,
517
+ content_md5: content_md5, issuer: issuer,
518
+ client_email: client_email,
519
+ signing_key: signing_key,
520
+ private_key: private_key, query: query
521
+ when :v4
522
+ signer = File::SignerV4.new bucket, path, service
523
+ signer.signed_url method: method, expires: expires,
524
+ headers: headers, issuer: issuer,
525
+ client_email: client_email,
526
+ signing_key: signing_key,
527
+ private_key: private_key, query: query
528
+ else
529
+ raise ArgumentError, "version '#{version}' not supported"
530
+ end
494
531
  end
495
532
 
496
533
  protected
@@ -16,7 +16,7 @@
16
16
  module Google
17
17
  module Cloud
18
18
  module Storage
19
- VERSION = "1.17.0".freeze
19
+ VERSION = "1.18.0".freeze
20
20
  end
21
21
  end
22
22
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: google-cloud-storage
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.17.0
4
+ version: 1.18.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mike Moore
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2019-02-07 00:00:00.000000000 Z
12
+ date: 2019-04-09 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: google-cloud-core
@@ -73,6 +73,20 @@ dependencies:
73
73
  - - "~>"
74
74
  - !ruby/object:Gem::Version
75
75
  version: '0.4'
76
+ - !ruby/object:Gem::Dependency
77
+ name: addressable
78
+ requirement: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 2.6.0
83
+ type: :runtime
84
+ prerelease: false
85
+ version_requirements: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 2.6.0
76
90
  - !ruby/object:Gem::Dependency
77
91
  name: minitest
78
92
  requirement: !ruby/object:Gem::Requirement
@@ -163,14 +177,14 @@ dependencies:
163
177
  requirements:
164
178
  - - "~>"
165
179
  - !ruby/object:Gem::Version
166
- version: 0.61.0
180
+ version: 0.64.0
167
181
  type: :development
168
182
  prerelease: false
169
183
  version_requirements: !ruby/object:Gem::Requirement
170
184
  requirements:
171
185
  - - "~>"
172
186
  - !ruby/object:Gem::Version
173
- version: 0.61.0
187
+ version: 0.64.0
174
188
  - !ruby/object:Gem::Dependency
175
189
  name: simplecov
176
190
  requirement: !ruby/object:Gem::Requirement
@@ -243,7 +257,8 @@ files:
243
257
  - lib/google/cloud/storage/file.rb
244
258
  - lib/google/cloud/storage/file/acl.rb
245
259
  - lib/google/cloud/storage/file/list.rb
246
- - lib/google/cloud/storage/file/signer.rb
260
+ - lib/google/cloud/storage/file/signer_v2.rb
261
+ - lib/google/cloud/storage/file/signer_v4.rb
247
262
  - lib/google/cloud/storage/file/verifier.rb
248
263
  - lib/google/cloud/storage/notification.rb
249
264
  - lib/google/cloud/storage/policy.rb
@@ -270,8 +285,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
270
285
  - !ruby/object:Gem::Version
271
286
  version: '0'
272
287
  requirements: []
273
- rubyforge_project:
274
- rubygems_version: 2.7.6
288
+ rubygems_version: 3.0.3
275
289
  signing_key:
276
290
  specification_version: 4
277
291
  summary: API Client library for Google Cloud Storage