Class: GcsSigner

Inherits:
Object
  • Object
show all
Defined in:
lib/gcs_signer.rb,
lib/gcs_signer/version.rb

Overview

Creates signed_url for a file on Google Cloud Storage.

signer = GcsSigner.new(path: "/Users/leo/private/service_account.json")
signer.sign "your-bucket", "object/name"
# => "https://storage.googleapis.com/your-bucket/object/name?..."

Defined Under Namespace

Classes: AuthError

Constant Summary collapse

DEFAULT_GCS_URL =
Addressable::URI.new(
  scheme: "https", host: "storage.googleapis.com"
).freeze
VERSION =
"0.4.1"

Instance Method Summary collapse

Constructor Details

#initialize(path: nil, keyfile_json: nil, gcs_url: DEFAULT_GCS_URL) ⇒ GcsSigner

gcs-signer requires credential that can access to GCS.

path

the path of the service_account json file.

keyfile_string

…or the content of the service_account json file.

gcs_url

Custom GCS url when signing a url.

or if you also use google-cloud gem. you can authenticate using environment variable that uses.



25
26
27
28
29
30
31
32
# File 'lib/gcs_signer.rb', line 25

def initialize(path: nil, keyfile_json: nil, gcs_url: DEFAULT_GCS_URL)
  keyfile_json ||= path.nil? ? look_for_environment_variables : File.read(path)
  fail AuthError, "No credentials given." if keyfile_json.nil?

  @credentials = JSON.parse(keyfile_json)
  @key = OpenSSL::PKey::RSA.new(@credentials["private_key"])
  @gcs_url = Addressable::URI.parse(gcs_url)
end

Instance Method Details

#inspectString

Prevents confidential information (like private key) from exposing when used with interactive shell such as pry and irb.

Returns:

  • (String)

    contains project_id and client_email



116
117
118
119
120
# File 'lib/gcs_signer.rb', line 116

def inspect
  "#<GcsSigner " \
  "project_id: #{@credentials['project_id']} " \
  "client_email: #{@credentials['client_email']}>"
end

#sign_url(bucket, key, version: :v2, **options) ⇒ String

Generates signed url.

bucket

the name of the Cloud Storage bucket that contains the object.

key

the name of the object for signed url.

Variable options are available:

version

signature version; :v2 or :v4

expires

Time(stamp in UTC) when the signed url expires.

valid_for

…or how much seconds is the signed url available.

response_content_disposition

Content-Disposition of the signed URL.

response_content_type

Content-Type of the signed URL.

If you set neither expires nor valid_for, it will set to 300 seconds by default.

# default is 5 minutes
signer.sign_url("bucket-name", "path/to/file")

# You can give Time object.
signer.sign_url("bucket-name", "path/to/file",
                 expires: Time.new(2016, 12, 26, 14, 31, 48, "+09:00"))

# You can give how much seconds is the signed url valid.
signer.sign_url("bucket", "path/to/file", valid_for: 30 * 60)

# If you use ActiveSupport, you can also do some magic.
signer.sign_url("bucket", "path/to/file", valid_for: 40.minutes)

Returns:

  • (String)

    Signed url



61
62
63
64
65
66
67
68
69
70
# File 'lib/gcs_signer.rb', line 61

def sign_url(bucket, key, version: :v2, **options)
  case version
  when :v2
    sign_url_v2(bucket, key, **options)
  when :v4
    sign_url_v4(bucket, key, **options)
  else
    fail ArgumentError, "Version not supported: #{version.inspect}"
  end
end

#sign_url_v2(bucket, key, method: "GET", valid_for: 300, **options) ⇒ Object



72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/gcs_signer.rb', line 72

def sign_url_v2(bucket, key, method: "GET", valid_for: 300, **options)
  url = @gcs_url + "./#{request_path(bucket, key)}"
  expires_at = options[:expires] || Time.now.utc.to_i + valid_for.to_i
  sign_payload = [method, "", "", expires_at.to_i, url.path].join("\n")

  url.query_values = (options[:params] || {}).merge(
    "GoogleAccessId" => @credentials["client_email"],
    "Expires" => expires_at.to_i,
    "Signature" => sign_v2(sign_payload),
    "response-content-disposition" => options[:response_content_disposition],
    "response-content-type" => options[:response_content_type]
  ).compact

  url.to_s
end

#sign_url_v4(bucket, key, method: "GET", headers: {}, **options) ⇒ Object



88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
# File 'lib/gcs_signer.rb', line 88

def sign_url_v4(bucket, key, method: "GET", headers: {}, **options)
  url = @gcs_url + "./#{request_path(bucket, key)}"
  time = Time.now.utc

  request_headers = headers.merge(host: @gcs_url.host).transform_keys(&:downcase)
  signed_headers = request_headers.keys.sort.join(";")
  scopes = [time.strftime("%Y%m%d"), "auto", "storage", "goog4_request"].join("/")

  url.query_values = build_query_params(time, scopes, signed_headers, **options)

  canonical_request = [
    method, url.path.to_s, url.query,
    *request_headers.sort.map { |header| header.join(":") },
    "", signed_headers, "UNSIGNED-PAYLOAD"
  ].join("\n")

  sign_payload = [
    "GOOG4-RSA-SHA256", time.strftime("%Y%m%dT%H%M%SZ"), scopes,
    Digest::SHA256.hexdigest(canonical_request)
  ].join("\n")

  url.query += "&X-Goog-Signature=#{sign_v4(sign_payload)}"
  url.to_s
end