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 +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
|