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 +4 -4
- data/CHANGELOG.md +9 -1
- data/lib/google/cloud/storage/bucket.rb +74 -32
- data/lib/google/cloud/storage/bucket/list.rb +1 -1
- data/lib/google/cloud/storage/file.rb +66 -30
- data/lib/google/cloud/storage/file/list.rb +1 -1
- data/lib/google/cloud/storage/file/{signer.rb → signer_v2.rb} +5 -7
- data/lib/google/cloud/storage/file/signer_v4.rb +182 -0
- data/lib/google/cloud/storage/file/verifier.rb +2 -2
- data/lib/google/cloud/storage/project.rb +65 -28
- data/lib/google/cloud/storage/version.rb +1 -1
- metadata +21 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 625405fb7ef792e7cd585f572f0e5102dfc6a03b4fb84daabce5fbe08b30fede
|
4
|
+
data.tar.gz: 93902f2000495402041a5a564956973b67e381326a68fe282bfdf6576b05d242
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5e6eb7b4c4ec6c710d97c9611804c851b61a6878f5a10ef138c43f1aa0c8eff862232edfd6ae46e8f614ca4315c682b7486eff51fb3ace86f417cb1d49e92aa4
|
7
|
+
data.tar.gz: 10a8d0a3c177e4b0c9265e81929fa7090463da4eb3c577ee88b3456a66712bf85fef5b6cb34e212cdbf7d5080432ed5679de1829f69d548999b24b436c9e7a3d
|
data/CHANGELOG.md
CHANGED
@@ -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
|
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
|
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
|
-
#
|
1357
|
-
#
|
1358
|
-
#
|
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
|
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
|
1376
|
-
#
|
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
|
-
#
|
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
|
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
|
-
#
|
1425
|
-
#
|
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.
|
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.
|
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
|
-
|
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
|
-
|
1457
|
-
|
1458
|
-
|
1459
|
-
|
1460
|
-
|
1461
|
-
|
1462
|
-
|
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::
|
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
|
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/
|
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
|
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
|
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
|
-
#
|
1388
|
-
#
|
1389
|
-
#
|
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
|
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
|
1405
|
-
#
|
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
|
-
#
|
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
|
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
|
1454
|
-
#
|
1455
|
-
|
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.
|
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
|
-
|
1487
|
-
|
1488
|
-
|
1489
|
-
|
1490
|
-
|
1491
|
-
|
1492
|
-
|
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
|
1741
|
-
Zlib::GzipReader.new
|
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
|
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
|
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
|
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
|
-
|
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
|
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
|
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
|
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
|
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
|
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
|
-
#
|
383
|
-
#
|
384
|
-
#
|
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
|
402
|
-
#
|
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
|
-
#
|
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
|
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
|
-
#
|
454
|
-
#
|
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.
|
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.
|
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
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
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
|
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.
|
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-
|
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.
|
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.
|
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/
|
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
|
-
|
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
|