google-cloud-storage 1.17.0 → 1.18.0

Sign up to get free protection for your applications and to get access to all the features.
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