gcs-signer 0.1.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: d9da9710c3ec0d2781bb3d0002fc104a9f898ad9
4
- data.tar.gz: 8027a48a1276f076f9d0a46a5bd9aa0841da712e
2
+ SHA256:
3
+ metadata.gz: 736ffe7843310671d2c07459e94ef47ad8b9ebfa24cf46530a5b1138cd954b42
4
+ data.tar.gz: 77ee7afb236a5416f13e1f40aec1e0d37339319476d2167e56febf10c19b2349
5
5
  SHA512:
6
- metadata.gz: 3b1303e21df8a93d2cd23a8ef1636a30d79ff3238643524adfc52efa7b082df53fdcb2d5fbdaa85e6a89b2ef06323d31d481a906106a895ca42bc6b7fb4687d5
7
- data.tar.gz: 8e37da7d200265cdeb64ae82a0395a6c66a26ae92b1f9bb7d1e25698b30a349be783d372114cacb2605a3238e9afcb99fa8909fd8f4586421dbe7edb77edda6f
6
+ metadata.gz: 97985b7cbcd77a932e92a861efc6df31199a56066c255333e41cd4f156364a726de676e6eb85c9e083400cd5321917fc62c39964a4199e8efa9977c2c9316127
7
+ data.tar.gz: 6007e855ec3c35a80444ca98e7f1056cc876236f0e07090acb57e61662b5cfd558e2aea208239b1ba4c285747c958ce1aae12c6533be58318b08956cd446ddc1
data/README.md CHANGED
@@ -4,9 +4,8 @@ Simple signed URL generator for Google Cloud Storage.
4
4
 
5
5
  ## Features
6
6
 
7
- * No additional gems required.
8
7
  * No network connection required to generate signed URL.
9
- * Can read JSON service_account credentials from environment variables. So it can be used with [google-cloud-ruby](https://github.com/GoogleCloudPlatform/google-cloud-ruby) without additional configurations.
8
+ * Can read JSON service-account credentials from environment variables. So it can be used with [google-cloud-ruby](https://github.com/GoogleCloudPlatform/google-cloud-ruby) without additional configurations.
10
9
 
11
10
  ## Installation
12
11
 
@@ -16,21 +15,33 @@ gem install gcs-signer
16
15
 
17
16
  ## Usage
18
17
 
19
- If you already configured `GOOGLE_CLOUD_KEYFILE` or `GOOGLE_CLOUD_KEYFILE_JSON` for google-cloud-ruby gem, just
18
+ ### Authentication
19
+
20
+ If you already configured `GOOGLE_APPLICATION_CREDENTIALS` for google-cloud-ruby gem, just
21
+
22
+ ```ruby
23
+ signer = GcsSigner.new
24
+ ```
25
+
26
+ You can also set `GOOGLE_CLOUD_KEYFILE_JSON` environment varialble to the content of service-account.json.
20
27
 
21
28
  ```ruby
29
+ puts ENV["GOOGLE_CLOUD_KEYFILE_JSON"]
30
+ # => { "type": "service_account", ...
22
31
  signer = GcsSigner.new
23
32
  ```
24
33
 
25
- or you can give path of the service_account json file, or contents of it.
34
+ or you can give path of the service account file, or contents of it without using environment variables.
26
35
 
27
36
  ```ruby
28
37
  signer = GcsSigner.new path: "/home/leo/path/to/service_account.json"
29
38
 
30
- signer = GcsSigner.new json_string: '{ "type": "service_account", ...'
39
+ signer = GcsSigner.new keyfile_json: '{ "type": "service_account", ...'
31
40
  ```
32
41
 
33
- then `#sign_url` to generate signed URL.
42
+ ### Signing URL
43
+
44
+ `#sign_url` to generate signed URL.
34
45
 
35
46
  ```ruby
36
47
  # The signed URL is valid for 5 minutes by default.
@@ -42,14 +53,16 @@ signer.sign_url "bucket-name", "object-name",
42
53
 
43
54
  signer.sign_url "bucket-name", "object_name", valid_for: 600
44
55
 
45
- # If you use AcriveSupport in your project, you can also do some magic like:
46
- signer.sign_url "buekct", "object", valid_for: 45.minutes
56
+ # If you use AcriveSupport in your project, you can use some sugar like:
57
+ signer.sign_url "bucket", "object", valid_for: 45.minutes
58
+ signer.sign_url "bucket", "object", expires_at: 5.minutes.from_now
59
+
60
+ # You can set response_content_disposition and response_content_type to change response headers.
61
+ signer.sign_url "bucket", "object", response_content_type: "video/mp4"
62
+ signer.sign_url "bucket", "object", response_content_disposition: "attachment; filename=video.mp4"
47
63
 
48
- # See https://cloud.google.com/storage/docs/access-control/signed-urls
49
- # for other avaliable options.
50
- signer.sign_url "buekct", "object", google_access_id: "sangwon@sha.kr",
51
- method: "PUT", content_type: "text/plain",
52
- md5: "beefbeef..."
64
+ # You can use V4 signing if you prefer longer URL
65
+ signer.sign_url "bucket", "object", version: :v4
53
66
  ```
54
67
 
55
68
  ## License
data/lib/gcs_signer.rb CHANGED
@@ -1,8 +1,9 @@
1
1
  # frozen_string_literal: true
2
- require "uri"
3
- require "openssl"
4
- require "base64"
2
+
5
3
  require "json"
4
+ require "base64"
5
+ require "openssl"
6
+ require "addressable"
6
7
 
7
8
  # Creates signed_url for a file on Google Cloud Storage.
8
9
  #
@@ -10,31 +11,39 @@ require "json"
10
11
  # signer.sign "your-bucket", "object/name"
11
12
  # # => "https://storage.googleapis.com/your-bucket/object/name?..."
12
13
  class GcsSigner
13
- VERSION = "0.1.0"
14
+ DEFAULT_GCS_URL = Addressable::URI.new(
15
+ scheme: "https", host: "storage.googleapis.com"
16
+ ).freeze
14
17
 
15
18
  # gcs-signer requires credential that can access to GCS.
16
19
  # [path] the path of the service_account json file.
17
- # [json_string] ...or the content of the service_account json file.
20
+ # [keyfile_string] ...or the content of the service_account json file.
21
+ # [gcs_url] Custom GCS url when signing a url.
18
22
  #
19
23
  # or if you also use \+google-cloud+ gem. you can authenticate
20
24
  # using environment variable that uses.
21
- def initialize(path: nil, json_string: nil)
22
- json_string ||= File.read(path) unless path.nil?
23
- json_string = look_for_environment_variables if json_string.nil?
25
+ def initialize(path: nil, keyfile_json: nil, gcs_url: DEFAULT_GCS_URL)
26
+ keyfile_json ||= path.nil? ? look_for_environment_variables : File.read(path)
27
+ fail AuthError, "No credentials given." if keyfile_json.nil?
24
28
 
25
- fail AuthError, "No credentials given." if json_string.nil?
26
- @credentials = JSON.parse(json_string)
29
+ @credentials = JSON.parse(keyfile_json)
27
30
  @key = OpenSSL::PKey::RSA.new(@credentials["private_key"])
31
+ @gcs_url = Addressable::URI.parse(gcs_url)
28
32
  end
29
33
 
30
34
  # @return [String] Signed url
31
35
  # Generates signed url.
32
36
  # [bucket] the name of the Cloud Storage bucket that contains the object.
33
- # [object_name] the name of the object for signed url..
37
+ # [key] the name of the object for signed url.
34
38
  # Variable options are available:
39
+ # [version] signature version; \+:v2+ or \+:v4+
35
40
  # [expires] Time(stamp in UTC) when the signed url expires.
36
41
  # [valid_for] ...or how much seconds is the signed url available.
37
- # If you don't set those values, it will set to 300 seconds by default.
42
+ # [response_content_disposition] Content-Disposition of the signed URL.
43
+ # [response_content_type] Content-Type of the signed URL.
44
+ #
45
+ # If you set neither \+expires+ nor \+valid_for+,
46
+ # it will set to 300 seconds by default.
38
47
  #
39
48
  # # default is 5 minutes
40
49
  # signer.sign_url("bucket-name", "path/to/file")
@@ -49,19 +58,55 @@ class GcsSigner
49
58
  # # If you use ActiveSupport, you can also do some magic.
50
59
  # signer.sign_url("bucket", "path/to/file", valid_for: 40.minutes)
51
60
  #
52
- # For details information of another options,
53
- # like \+method+, \+md5+, and \+content_type+. See:
54
- # https://cloud.google.com/storage/docs/access-control/signed-urls
55
- #
56
- # Just in case if you want to change \+GoogleAccessId+ in the signed url,
57
- # You can set \+google_access_id+ as an option.
58
- def sign_url(bucket, object_name, options = {})
59
- options = apply_default_options(options)
60
-
61
- url = URI.join "https://storage.googleapis.com",
62
- URI.escape("/#{bucket}/"), URI.escape(object_name)
63
- signature = sign string_that_will_be_signed(url, options)
64
- url.query = query_for_signed_url(signature, options)
61
+ def sign_url(bucket, key, version: :v2, **options)
62
+ case version
63
+ when :v2
64
+ sign_url_v2(bucket, key, **options)
65
+ when :v4
66
+ sign_url_v4(bucket, key, **options)
67
+ else
68
+ fail ArgumentError, "Version not supported: #{version.inspect}"
69
+ end
70
+ end
71
+
72
+ def sign_url_v2(bucket, key, method: "GET", valid_for: 300, **options)
73
+ url = @gcs_url + "./#{request_path(bucket, key)}"
74
+ expires_at = options[:expires] || Time.now.utc.to_i + valid_for.to_i
75
+ sign_payload = [method, "", "", expires_at.to_i, url.path].join("\n")
76
+
77
+ url.query_values = (options[:params] || {}).merge(
78
+ "GoogleAccessId" => @credentials["client_email"],
79
+ "Expires" => expires_at.to_i,
80
+ "Signature" => sign_v2(sign_payload),
81
+ "response-content-disposition" => options[:response_content_disposition],
82
+ "response-content-type" => options[:response_content_type]
83
+ ).compact
84
+
85
+ url.to_s
86
+ end
87
+
88
+ def sign_url_v4(bucket, key, method: "GET", headers: {}, **options)
89
+ url = @gcs_url + "./#{request_path(bucket, key)}"
90
+ time = Time.now.utc
91
+
92
+ request_headers = headers.merge(host: @gcs_url.host).transform_keys(&:downcase)
93
+ signed_headers = request_headers.keys.sort.join(";")
94
+ scopes = [time.strftime("%Y%m%d"), "auto", "storage", "goog4_request"].join("/")
95
+
96
+ url.query_values = build_query_params(time, scopes, signed_headers, **options)
97
+
98
+ canonical_request = [
99
+ method, url.path.to_s, url.query,
100
+ *request_headers.sort.map { |header| header.join(":") },
101
+ "", signed_headers, "UNSIGNED-PAYLOAD"
102
+ ].join("\n")
103
+
104
+ sign_payload = [
105
+ "GOOG4-RSA-SHA256", time.strftime("%Y%m%dT%H%M%SZ"), scopes,
106
+ Digest::SHA256.hexdigest(canonical_request)
107
+ ].join("\n")
108
+
109
+ url.query += "&X-Goog-Signature=#{sign_v4(sign_payload)}"
65
110
  url.to_s
66
111
  end
67
112
 
@@ -76,47 +121,51 @@ class GcsSigner
76
121
 
77
122
  private
78
123
 
79
- # Look for environment variable which stores service_account.
80
124
  def look_for_environment_variables
81
- unless ENV["GOOGLE_CLOUD_KEYFILE"].nil?
82
- return File.read(ENV["GOOGLE_CLOUD_KEYFILE"])
83
- end
125
+ env_keyfile_path = ENV["GOOGLE_CLOUD_KEYFILE"] || ENV["GOOGLE_APPLICATION_CREDENTIALS"]
126
+ env_keyfile_path.nil? ? ENV["GOOGLE_CLOUD_KEYFILE_JSON"] : File.read(env_keyfile_path)
127
+ end
84
128
 
85
- ENV["GOOGLE_CLOUD_KEYFILE_JSON"]
129
+ def request_path(bucket, object)
130
+ [
131
+ bucket, *object.split("/")
132
+ ].map do |str|
133
+ Addressable::URI.encode_component(str, Addressable::URI::CharacterClasses::UNRESERVED)
134
+ end.join("/")
86
135
  end
87
136
 
88
- # Signs the string with the given private key.
89
- def sign(string)
90
- @key.sign OpenSSL::Digest::SHA256.new, string
137
+ def sign_v2(string)
138
+ Base64.strict_encode64(sign(string))
91
139
  end
92
140
 
93
- def apply_default_options(options)
94
- {
95
- method: "GET", content_md5: nil,
96
- content_type: nil,
97
- expires: Time.now.utc.to_i + (options[:valid_for] || 300).to_i,
98
- google_access_id: @credentials["client_email"]
99
- }.merge(options)
141
+ def sign_v4(string)
142
+ sign(string).unpack1("H*")
100
143
  end
101
144
 
102
- def string_that_will_be_signed(url, options)
103
- [
104
- options[:method],
105
- options[:content_md5],
106
- options[:content_type],
107
- options[:expires].to_i,
108
- url.path
109
- ].join "\n"
145
+ # Signs the string with the given private key.
146
+ def sign(string)
147
+ @key.sign(OpenSSL::Digest.new("SHA256"), string)
110
148
  end
111
149
 
112
- # Escapes and generates query string for actual result.
113
- def query_for_signed_url(signature, options)
114
- URI.encode_www_form GoogleAccessId: options[:google_access_id],
115
- Expires: options[:expires].to_i,
116
- Signature: Base64.strict_encode64(signature)
150
+ # only used in v4
151
+ def build_query_params(time, scopes, signed_headers, valid_for: 300, **options)
152
+ goog_expires = if options[:expires]
153
+ options[:expires].to_i - time.to_i
154
+ else
155
+ valid_for.to_i
156
+ end.clamp(0, 604_800)
157
+
158
+ (options[:params] || {}).merge(
159
+ "X-Goog-Algorithm" => "GOOG4-RSA-SHA256",
160
+ "X-Goog-Credential" => [@credentials["client_email"], scopes].join("/"),
161
+ "X-Goog-Date" => time.strftime("%Y%m%dT%H%M%SZ"),
162
+ "X-Goog-Expires" => goog_expires,
163
+ "X-Goog-SignedHeaders" => signed_headers,
164
+ "response-content-disposition" => options[:response_content_disposition],
165
+ "response-content-type" => options[:response_content_type]
166
+ ).compact.sort
117
167
  end
118
168
 
119
169
  # raised When GcsSigner could not find service_account JSON file.
120
- class AuthError < StandardError
121
- end
170
+ class AuthError < StandardError; end
122
171
  end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ class GcsSigner
4
+ VERSION = "0.4.1"
5
+ end
metadata CHANGED
@@ -1,90 +1,108 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gcs-signer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.4.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sangwon Yi
8
- autorequire:
8
+ - Minku Lee
9
+ - Larry Kim
10
+ autorequire:
9
11
  bindir: bin
10
12
  cert_chain: []
11
- date: 2016-12-27 00:00:00.000000000 Z
13
+ date: 2021-02-03 00:00:00.000000000 Z
12
14
  dependencies:
13
15
  - !ruby/object:Gem::Dependency
14
- name: rake
16
+ name: addressable
15
17
  requirement: !ruby/object:Gem::Requirement
16
18
  requirements:
17
19
  - - "~>"
18
20
  - !ruby/object:Gem::Version
19
- version: '12.0'
20
- type: :development
21
+ version: '2.7'
22
+ type: :runtime
21
23
  prerelease: false
22
24
  version_requirements: !ruby/object:Gem::Requirement
23
25
  requirements:
24
26
  - - "~>"
25
27
  - !ruby/object:Gem::Version
26
- version: '12.0'
28
+ version: '2.7'
27
29
  - !ruby/object:Gem::Dependency
28
30
  name: pry
29
31
  requirement: !ruby/object:Gem::Requirement
30
32
  requirements:
31
33
  - - "~>"
32
34
  - !ruby/object:Gem::Version
33
- version: '0.9'
35
+ version: '0.11'
36
+ type: :development
37
+ prerelease: false
38
+ version_requirements: !ruby/object:Gem::Requirement
39
+ requirements:
40
+ - - "~>"
41
+ - !ruby/object:Gem::Version
42
+ version: '0.11'
43
+ - !ruby/object:Gem::Dependency
44
+ name: rake
45
+ requirement: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - "~>"
48
+ - !ruby/object:Gem::Version
49
+ version: '12.3'
34
50
  type: :development
35
51
  prerelease: false
36
52
  version_requirements: !ruby/object:Gem::Requirement
37
53
  requirements:
38
54
  - - "~>"
39
55
  - !ruby/object:Gem::Version
40
- version: '0.9'
56
+ version: '12.3'
41
57
  - !ruby/object:Gem::Dependency
42
58
  name: rubocop
43
59
  requirement: !ruby/object:Gem::Requirement
44
60
  requirements:
45
61
  - - "~>"
46
62
  - !ruby/object:Gem::Version
47
- version: 0.40.0
63
+ version: '1.0'
48
64
  type: :development
49
65
  prerelease: false
50
66
  version_requirements: !ruby/object:Gem::Requirement
51
67
  requirements:
52
68
  - - "~>"
53
69
  - !ruby/object:Gem::Version
54
- version: 0.40.0
70
+ version: '1.0'
55
71
  description: |2
56
72
  Simple signed URL generator for Google Cloud Storage.
57
- No additional gems and API requests required to generate signed URL.
73
+ No API requests required to generate signed URL.
58
74
  email:
59
75
  - sangwon@sha.kr
76
+ - minku@sha.kr
77
+ - larry@sha.kr
60
78
  executables: []
61
79
  extensions: []
62
80
  extra_rdoc_files: []
63
81
  files:
64
82
  - README.md
65
83
  - lib/gcs_signer.rb
84
+ - lib/gcs_signer/version.rb
66
85
  homepage: https://github.com/shakrmedia/gcs-signer
67
86
  licenses:
68
87
  - MIT
69
88
  metadata: {}
70
- post_install_message:
89
+ post_install_message:
71
90
  rdoc_options: []
72
91
  require_paths:
73
92
  - lib
74
93
  required_ruby_version: !ruby/object:Gem::Requirement
75
94
  requirements:
76
- - - "~>"
95
+ - - ">"
77
96
  - !ruby/object:Gem::Version
78
- version: '2.2'
97
+ version: '2.6'
79
98
  required_rubygems_version: !ruby/object:Gem::Requirement
80
99
  requirements:
81
100
  - - ">="
82
101
  - !ruby/object:Gem::Version
83
102
  version: '0'
84
103
  requirements: []
85
- rubyforge_project:
86
- rubygems_version: 2.5.2
87
- signing_key:
104
+ rubygems_version: 3.2.7
105
+ signing_key:
88
106
  specification_version: 4
89
- summary: Simple signed URL generator for Google Cloud Storage.
107
+ summary: Simple URL signer for Google Cloud Storage.
90
108
  test_files: []