cloudinary 1.18.0 → 1.21.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,9 +1,14 @@
1
1
  class Cloudinary::Search
2
+ SORT_BY = :sort_by
3
+ AGGREGATE = :aggregate
4
+ WITH_FIELD = :with_field
5
+ KEYS_WITH_UNIQUE_VALUES = [SORT_BY, AGGREGATE, WITH_FIELD].freeze
6
+
2
7
  def initialize
3
8
  @query_hash = {
4
- :sort_by => [],
5
- :aggregate => [],
6
- :with_field => []
9
+ SORT_BY => {},
10
+ AGGREGATE => {},
11
+ WITH_FIELD => {}
7
12
  }
8
13
  end
9
14
 
@@ -28,23 +33,51 @@ class Cloudinary::Search
28
33
  self
29
34
  end
30
35
 
36
+ # Sets the `sort_by` field.
37
+ #
38
+ # @param [String] field_name The field to sort by. You can specify more than one sort_by parameter;
39
+ # results will be sorted according to the order of the fields provided.
40
+ # @param [String] dir Sort direction. Valid sort directions are 'asc' or 'desc'. Default: 'desc'.
41
+ #
42
+ # @return [Cloudinary::Search]
31
43
  def sort_by(field_name, dir = 'desc')
32
- @query_hash[:sort_by].push(field_name => dir)
44
+ @query_hash[SORT_BY][field_name] = { field_name => dir }
33
45
  self
34
46
  end
35
47
 
48
+ # The name of a field (attribute) for which an aggregation count should be calculated and returned in the response.
49
+ #
50
+ # You can specify more than one aggregate parameter.
51
+ #
52
+ # @param [String] value Supported values: resource_type, type, pixels (only the image assets in the response are
53
+ # aggregated), duration (only the video assets in the response are aggregated), format, and
54
+ # bytes. For aggregation fields without discrete values, the results are divided into
55
+ # categories.
56
+ # @return [Cloudinary::Search]
36
57
  def aggregate(value)
37
- @query_hash[:aggregate].push(value)
58
+ @query_hash[AGGREGATE][value] = value
38
59
  self
39
60
  end
40
61
 
62
+ # The name of an additional asset attribute to include for each asset in the response.
63
+ #
64
+ # @param [String] value Possible value: context, tags, and for Tier 2 also image_metadata, and image_analysis.
65
+ #
66
+ # @return [Cloudinary::Search]
41
67
  def with_field(value)
42
- @query_hash[:with_field].push(value)
68
+ @query_hash[WITH_FIELD][value] = value
43
69
  self
44
70
  end
45
71
 
72
+ # Returns the query as an hash.
73
+ #
74
+ # @return [Hash]
46
75
  def to_h
47
- @query_hash.select { |_, value| !value.nil? && !(value.is_a?(Array) && value.empty?) }
76
+ @query_hash.each_with_object({}) do |(key, value), query|
77
+ next if value.nil? || ((value.is_a?(Array) || value.is_a?(Hash)) && value.blank?)
78
+
79
+ query[key] = KEYS_WITH_UNIQUE_VALUES.include?(key) ? value.values : value
80
+ end
48
81
  end
49
82
 
50
83
  def execute(options = {})
@@ -42,6 +42,7 @@ class Cloudinary::Uploader
42
42
  :faces => Cloudinary::Utils.as_safe_bool(options[:faces]),
43
43
  :folder => options[:folder],
44
44
  :format => options[:format],
45
+ :filename_override => options[:filename_override],
45
46
  :headers => build_custom_headers(options[:headers]),
46
47
  :image_metadata => Cloudinary::Utils.as_safe_bool(options[:image_metadata]),
47
48
  :invalidate => Cloudinary::Utils.as_safe_bool(options[:invalidate]),
@@ -158,7 +159,9 @@ class Cloudinary::Uploader
158
159
  :from_public_id => from_public_id,
159
160
  :to_public_id => to_public_id,
160
161
  :to_type => options[:to_type],
161
- :invalidate => Cloudinary::Utils.as_safe_bool(options[:invalidate])
162
+ :invalidate => Cloudinary::Utils.as_safe_bool(options[:invalidate]),
163
+ :context => options[:context],
164
+ :metadata => options[:metadata]
162
165
  }
163
166
  end
164
167
  end
@@ -206,17 +209,42 @@ class Cloudinary::Uploader
206
209
  end
207
210
  end
208
211
 
209
- def self.generate_sprite(tag, options={})
212
+ SLIDESHOW_PARAMS = [:notification_url, :public_id, :upload_preset]
213
+
214
+ # Creates auto-generated video slideshow.
215
+ #
216
+ # @param [Hash] options Additional options.
217
+ #
218
+ # @return [Hash] Hash with meta information URLs of generated slideshow resources.
219
+ def self.create_slideshow(options = {})
220
+ options[:resource_type] ||= :video
221
+
222
+ call_api("create_slideshow", options) do
223
+ params = {
224
+ :timestamp => Time.now.to_i,
225
+ :transformation => Cloudinary::Utils.build_eager(options[:transformation]),
226
+ :manifest_transformation => Cloudinary::Utils.build_eager(options[:manifest_transformation]),
227
+ :manifest_json => options[:manifest_json] && options[:manifest_json].to_json,
228
+ :tags => options[:tags] && Cloudinary::Utils.build_array(options[:tags]).join(","),
229
+ :overwrite => Cloudinary::Utils.as_safe_bool(options[:overwrite])
230
+ }
231
+ SLIDESHOW_PARAMS.each { |k| params[k] = options[k] unless options[k].nil? }
232
+
233
+ params
234
+ end
235
+ end
236
+
237
+ # Generates sprites by merging multiple images into a single large image.
238
+ #
239
+ # @param [String|Hash] tag Treated as additional options when hash is passed, otherwise as a tag
240
+ # @param [Hash] options Additional options. Should be omitted when +tag_or_options+ is a Hash
241
+ #
242
+ # @return [Hash] Hash with meta information URLs of generated sprite resources
243
+ def self.generate_sprite(tag, options = {})
210
244
  version_store = options.delete(:version_store)
211
245
 
212
246
  result = call_api("sprite", options) do
213
- {
214
- :timestamp => (options[:timestamp] || Time.now.to_i),
215
- :tag => tag,
216
- :async => options[:async],
217
- :notification_url => options[:notification_url],
218
- :transformation => Cloudinary::Utils.generate_transformation_string(options.merge(:fetch_format => options[:format]))
219
- }
247
+ Cloudinary::Utils.build_multi_and_sprite_params(tag, options)
220
248
  end
221
249
 
222
250
  if version_store == :file && result && result["version"]
@@ -228,16 +256,15 @@ class Cloudinary::Uploader
228
256
  return result
229
257
  end
230
258
 
231
- def self.multi(tag, options={})
259
+ # Creates either a single animated image, video or a PDF.
260
+ #
261
+ # @param [String|Hash] tag Treated as additional options when hash is passed, otherwise as a tag
262
+ # @param [Hash] options Additional options. Should be omitted when +tag_or_options+ is a Hash
263
+ #
264
+ # @return [Hash] Hash with meta information URLs of the generated file
265
+ def self.multi(tag, options = {})
232
266
  call_api("multi", options) do
233
- {
234
- :timestamp => (options[:timestamp] || Time.now.to_i),
235
- :tag => tag,
236
- :format => options[:format],
237
- :async => options[:async],
238
- :notification_url => options[:notification_url],
239
- :transformation => Cloudinary::Utils.generate_transformation_string(options.clone)
240
- }
267
+ Cloudinary::Utils.build_multi_and_sprite_params(tag, options)
241
268
  end
242
269
  end
243
270
 
@@ -341,11 +368,13 @@ class Cloudinary::Uploader
341
368
  non_signable ||= []
342
369
 
343
370
  unless options[:unsigned]
344
- api_key = options[:api_key] || Cloudinary.config.api_key || raise(CloudinaryException, "Must supply api_key")
345
- api_secret = options[:api_secret] || Cloudinary.config.api_secret || raise(CloudinaryException, "Must supply api_secret")
346
- params[:signature] = Cloudinary::Utils.api_sign_request(params.reject { |k, v| non_signable.include?(k) }, api_secret)
347
- params[:api_key] = api_key
371
+ api_key = options[:api_key] || Cloudinary.config.api_key || raise(CloudinaryException, "Must supply api_key")
372
+ api_secret = options[:api_secret] || Cloudinary.config.api_secret || raise(CloudinaryException, "Must supply api_secret")
373
+ signature_algorithm = options[:signature_algorithm]
374
+ params[:signature] = Cloudinary::Utils.api_sign_request(params.reject { |k, v| non_signable.include?(k) }, api_secret, signature_algorithm)
375
+ params[:api_key] = api_key
348
376
  end
377
+ proxy = options[:api_proxy] || Cloudinary.config.api_proxy
349
378
  timeout = options.fetch(:timeout) { Cloudinary.config.to_h.fetch(:timeout, 60) }
350
379
 
351
380
  result = nil
@@ -355,7 +384,7 @@ class Cloudinary::Uploader
355
384
  headers['Content-Range'] = options[:content_range] if options[:content_range]
356
385
  headers['X-Unique-Upload-Id'] = options[:unique_upload_id] if options[:unique_upload_id]
357
386
  headers.merge!(options[:extra_headers]) if options[:extra_headers]
358
- RestClient::Request.execute(:method => :post, :url => api_url, :payload => params.reject { |k, v| v.nil? || v=="" }, :timeout => timeout, :headers => headers) do
387
+ RestClient::Request.execute(:method => :post, :url => api_url, :payload => params.reject { |k, v| v.nil? || v=="" }, :timeout => timeout, :headers => headers, :proxy => proxy) do
359
388
  |response, request, tmpresult|
360
389
  raise CloudinaryException, "Server returned unexpected status code - #{response.code} - #{response.body}" unless [200, 400, 401, 403, 404, 500].include?(response.code)
361
390
  begin
@@ -13,6 +13,7 @@ require 'cloudinary/responsive'
13
13
  class Cloudinary::Utils
14
14
  # @deprecated Use Cloudinary::SHARED_CDN
15
15
  SHARED_CDN = Cloudinary::SHARED_CDN
16
+ MODE_DOWNLOAD = "download"
16
17
  DEFAULT_RESPONSIVE_WIDTH_TRANSFORMATION = {:width => :auto, :crop => :limit}
17
18
  CONDITIONAL_OPERATORS = {
18
19
  "=" => 'eq',
@@ -32,19 +33,34 @@ class Cloudinary::Utils
32
33
 
33
34
  PREDEFINED_VARS = {
34
35
  "aspect_ratio" => "ar",
36
+ "aspectRatio" => "ar",
35
37
  "current_page" => "cp",
38
+ "currentPage" => "cp",
36
39
  "face_count" => "fc",
40
+ "faceCount" => "fc",
37
41
  "height" => "h",
38
42
  "initial_aspect_ratio" => "iar",
43
+ "initialAspectRatio" => "iar",
44
+ "trimmed_aspect_ratio" => "tar",
45
+ "trimmedAspectRatio" => "tar",
39
46
  "initial_height" => "ih",
47
+ "initialHeight" => "ih",
40
48
  "initial_width" => "iw",
49
+ "initialWidth" => "iw",
41
50
  "page_count" => "pc",
51
+ "pageCount" => "pc",
42
52
  "page_x" => "px",
53
+ "pageX" => "px",
43
54
  "page_y" => "py",
55
+ "pageY" => "py",
44
56
  "tags" => "tags",
45
57
  "initial_duration" => "idu",
58
+ "initialDuration" => "idu",
46
59
  "duration" => "du",
47
- "width" => "w"
60
+ "width" => "w",
61
+ "illustration_score" => "ils",
62
+ "illustrationScore" => "ils",
63
+ "context" => "ctx"
48
64
  }
49
65
 
50
66
  SIMPLE_TRANSFORMATION_PARAMS = {
@@ -144,6 +160,16 @@ class Cloudinary::Utils
144
160
  LONG_URL_SIGNATURE_LENGTH = 32
145
161
  SHORT_URL_SIGNATURE_LENGTH = 8
146
162
 
163
+ UPLOAD_PREFIX = 'https://api.cloudinary.com'
164
+
165
+ ALGO_SHA1 = :sha1
166
+ ALGO_SHA256 = :sha256
167
+
168
+ ALGORITHM_SIGNATURE = {
169
+ ALGO_SHA1 => Digest::SHA1,
170
+ ALGO_SHA256 => Digest::SHA256,
171
+ }
172
+
147
173
  def self.extract_config_params(options)
148
174
  options.select{|k,v| URL_KEYS.include?(k)}
149
175
  end
@@ -309,16 +335,16 @@ class Cloudinary::Utils
309
335
  "if_" + normalize_expression(if_value) unless if_value.to_s.empty?
310
336
  end
311
337
 
312
- EXP_REGEXP = Regexp.new('(?<!\$)('+PREDEFINED_VARS.keys.join("|")+')'+'|('+CONDITIONAL_OPERATORS.keys.reverse.map { |k| Regexp.escape(k) }.join('|')+')(?=[ _])')
338
+ EXP_REGEXP = Regexp.new('(\$_*[^_ ]+)|(?<![\$:])('+PREDEFINED_VARS.keys.join("|")+')'+'|('+CONDITIONAL_OPERATORS.keys.reverse.map { |k| Regexp.escape(k) }.join('|')+')(?=[ _])')
313
339
  EXP_REPLACEMENT = PREDEFINED_VARS.merge(CONDITIONAL_OPERATORS)
314
340
 
315
341
  def self.normalize_expression(expression)
316
342
  if expression.nil?
317
- ''
343
+ nil
318
344
  elsif expression.is_a?( String) && expression =~ /^!.+!$/ # quoted string
319
345
  expression
320
346
  else
321
- expression.to_s.gsub(EXP_REGEXP,EXP_REPLACEMENT).gsub(/[ _]+/, "_")
347
+ expression.to_s.gsub(EXP_REGEXP) { |match| EXP_REPLACEMENT[match] || match }.gsub(/[ _]+/, "_")
322
348
  end
323
349
  end
324
350
 
@@ -405,6 +431,8 @@ class Cloudinary::Utils
405
431
  ]
406
432
 
407
433
  def self.text_style(layer)
434
+ return layer[:text_style] if layer[:text_style].present?
435
+
408
436
  font_family = layer[:font_family]
409
437
  font_size = layer[:font_size]
410
438
  keywords = []
@@ -433,9 +461,9 @@ class Cloudinary::Utils
433
461
  params_to_sign.map{|k,v| [k.to_s, v.is_a?(Array) ? v.join(",") : v]}.reject{|k,v| v.nil? || v == ""}.sort_by(&:first).map{|k,v| "#{k}=#{v}"}.join("&")
434
462
  end
435
463
 
436
- def self.api_sign_request(params_to_sign, api_secret)
464
+ def self.api_sign_request(params_to_sign, api_secret, signature_algorithm = nil)
437
465
  to_sign = api_string_to_sign(params_to_sign)
438
- Digest::SHA1.hexdigest("#{to_sign}#{api_secret}")
466
+ hash("#{to_sign}#{api_secret}", signature_algorithm, :hexdigest)
439
467
  end
440
468
 
441
469
  # Returns a JSON array as String.
@@ -501,6 +529,7 @@ class Cloudinary::Utils
501
529
  use_root_path = config_option_consume(options, :use_root_path)
502
530
  auth_token = config_option_consume(options, :auth_token)
503
531
  long_url_signature = config_option_consume(options, :long_url_signature)
532
+ signature_algorithm = config_option_consume(options, :signature_algorithm)
504
533
  unless auth_token == false
505
534
  auth_token = Cloudinary::AuthToken.merge_auth_token(Cloudinary.config.auth_token, auth_token)
506
535
  end
@@ -545,7 +574,10 @@ class Cloudinary::Utils
545
574
  raise(CloudinaryException, "Must supply api_secret") if (secret.nil? || secret.empty?)
546
575
  to_sign = [transformation, sign_version && version, source_to_sign].reject(&:blank?).join("/")
547
576
  to_sign = fully_unescape(to_sign)
548
- signature = compute_signature(to_sign, secret, long_url_signature)
577
+ signature_algorithm = long_url_signature ? ALGO_SHA256 : signature_algorithm
578
+ hash = hash("#{to_sign}#{secret}", signature_algorithm)
579
+ signature = Base64.urlsafe_encode64(hash)
580
+ signature = "s--#{signature[0, long_url_signature ? LONG_URL_SIGNATURE_LENGTH : SHORT_URL_SIGNATURE_LENGTH ]}--"
549
581
  end
550
582
 
551
583
  prefix = unsigned_download_url_prefix(source, cloud_name, private_cdn, cdn_subdomain, secure_cdn_subdomain, cname, secure, secure_distribution)
@@ -661,22 +693,72 @@ class Cloudinary::Utils
661
693
  prefix
662
694
  end
663
695
 
696
+ # Creates a base URL for the cloudinary api
697
+ #
698
+ # @param [Object] path Resource name
699
+ # @param [Hash] options Additional options
700
+ #
701
+ # @return [String]
702
+ def self.base_api_url(path, options = {})
703
+ cloudinary = options[:upload_prefix] || Cloudinary.config.upload_prefix || UPLOAD_PREFIX
704
+ cloud_name = options[:cloud_name] || Cloudinary.config.cloud_name || raise(CloudinaryException, 'Must supply cloud_name')
705
+
706
+ [cloudinary, 'v1_1', cloud_name, path].join('/')
707
+ end
708
+
664
709
  def self.cloudinary_api_url(action = 'upload', options = {})
665
- cloudinary = options[:upload_prefix] || Cloudinary.config.upload_prefix || "https://api.cloudinary.com"
666
- cloud_name = options[:cloud_name] || Cloudinary.config.cloud_name || raise(CloudinaryException, "Must supply cloud_name")
667
- resource_type = options[:resource_type] || "image"
668
- return [cloudinary, "v1_1", cloud_name, resource_type, action].join("/")
710
+ resource_type = options[:resource_type] || 'image'
711
+
712
+ base_api_url([resource_type, action], options)
669
713
  end
670
714
 
671
715
  def self.sign_request(params, options={})
672
716
  api_key = options[:api_key] || Cloudinary.config.api_key || raise(CloudinaryException, "Must supply api_key")
673
717
  api_secret = options[:api_secret] || Cloudinary.config.api_secret || raise(CloudinaryException, "Must supply api_secret")
718
+ signature_algorithm = options[:signature_algorithm]
674
719
  params = params.reject{|k, v| self.safe_blank?(v)}
675
- params[:signature] = Cloudinary::Utils.api_sign_request(params, api_secret)
720
+ params[:signature] = api_sign_request(params, api_secret, signature_algorithm)
676
721
  params[:api_key] = api_key
677
722
  params
678
723
  end
679
724
 
725
+ # Helper method for generating download URLs
726
+ #
727
+ # @param [String] action @see Cloudinary::Utils.cloudinary_api_url
728
+ # @param [Hash] params Query parameters in generated URL
729
+ # @param [Hash] options Additional options
730
+ # @yield [query_parameters] Invokes the block with query parameters to override how to encode them
731
+ #
732
+ # @return [String]
733
+ def self.cloudinary_api_download_url(action, params, options = {})
734
+ cloudinary_params = sign_request(params.merge(mode: MODE_DOWNLOAD), options)
735
+
736
+ "#{Cloudinary::Utils.cloudinary_api_url(action, options)}?#{hash_query_params(cloudinary_params)}"
737
+ end
738
+ private_class_method :cloudinary_api_download_url
739
+
740
+ # Return a signed URL to the 'generate_sprite' endpoint with 'mode=download'.
741
+ #
742
+ # @param [String|Hash] tag Treated as additional options when hash is passed, otherwise as a tag
743
+ # @param [Hash] options Additional options. Should be omitted when +tag_or_options+ is a Hash
744
+ #
745
+ # @return [String] The signed URL to download sprite
746
+ def self.download_generated_sprite(tag, options = {})
747
+ params = build_multi_and_sprite_params(tag, options)
748
+ cloudinary_api_download_url("sprite", params, options)
749
+ end
750
+
751
+ # Return a signed URL to the 'multi' endpoint with 'mode=download'.
752
+ #
753
+ # @param [String|Hash] tag Treated as additional options when hash is passed, otherwise as a tag
754
+ # @param [Hash] options Additional options. Should be omitted when +tag_or_options+ is a Hash
755
+ #
756
+ # @return [String] The signed URL to download multi
757
+ def self.download_multi(tag, options = {})
758
+ params = build_multi_and_sprite_params(tag, options)
759
+ cloudinary_api_download_url("multi", params, options)
760
+ end
761
+
680
762
  def self.private_download_url(public_id, format, options = {})
681
763
  cloudinary_params = sign_request({
682
764
  :timestamp=>Time.now.to_i,
@@ -725,11 +807,10 @@ class Cloudinary::Utils
725
807
  # @option options [String] :keep_derived (false) keep the derived images used for generating the archive
726
808
  # @return [String] archive url
727
809
  def self.download_archive_url(options = {})
728
- cloudinary_params = sign_request(Cloudinary::Utils.archive_params(options.merge(:mode => "download")), options)
729
- return Cloudinary::Utils.cloudinary_api_url("generate_archive", options) + "?" + hash_query_params(cloudinary_params)
810
+ params = Cloudinary::Utils.archive_params(options)
811
+ cloudinary_api_download_url("generate_archive", params, options)
730
812
  end
731
813
 
732
-
733
814
  # Returns a URL that when invokes creates an zip archive and returns it.
734
815
  # @see download_archive_url
735
816
  def self.download_zip_url(options = {})
@@ -1149,23 +1230,129 @@ class Cloudinary::Utils
1149
1230
  REMOTE_URL_REGEX === url
1150
1231
  end
1151
1232
 
1152
- # Computes a short or long signature based on a message and secret
1153
- # @param [String] message The string to sign
1154
- # @param [String] secret A secret that will be added to the message when signing
1155
- # @param [Boolean] long_signature Whether to create a short or long signature
1156
- # @return [String] Properly formatted signature
1157
- def self.compute_signature(message, secret, long_url_signature)
1158
- combined_message_secret = message + secret
1159
-
1160
- algo, signature_length =
1161
- if long_url_signature
1162
- [Digest::SHA256, LONG_URL_SIGNATURE_LENGTH]
1233
+ # Build params for multi, download_multi, generate_sprite, and download_generated_sprite methods
1234
+ #
1235
+ # @param [String|Hash] tag_or_options Treated as additional options when hash is passed, otherwise as a tag
1236
+ # @param [Hash] options Additional options. Should be omitted when +tag_or_options+ is a Hash
1237
+ #
1238
+ # @return [Hash]
1239
+ #
1240
+ # @private
1241
+ def self.build_multi_and_sprite_params(tag_or_options, options)
1242
+ if tag_or_options.is_a?(Hash)
1243
+ if options.blank?
1244
+ options = tag_or_options
1245
+ tag_or_options = nil
1163
1246
  else
1164
- [Digest::SHA1, SHORT_URL_SIGNATURE_LENGTH]
1247
+ raise "First argument must be a tag when additional options are passed"
1165
1248
  end
1249
+ end
1250
+ urls = options.delete(:urls)
1166
1251
 
1167
- "s--#{Base64.urlsafe_encode64(algo.digest(combined_message_secret))[0, signature_length]}--"
1252
+ if tag_or_options.blank? && urls.blank?
1253
+ raise "Either tag or urls are required"
1254
+ end
1255
+
1256
+ {
1257
+ :tag => tag_or_options,
1258
+ :urls => urls,
1259
+ :transformation => Cloudinary::Utils.generate_transformation_string(options.merge(:fetch_format => options[:format])),
1260
+ :notification_url => options[:notification_url],
1261
+ :format => options[:format],
1262
+ :async => options[:async],
1263
+ :mode => options[:mode],
1264
+ :timestamp => (options[:timestamp] || Time.now.to_i)
1265
+ }
1266
+ end
1267
+
1268
+ # The returned url should allow downloading the backedup asset based on the version and asset id
1269
+ #
1270
+ # asset and version id are returned with resource(<PUBLIC_ID1>, { versions: true })
1271
+ #
1272
+ # @param [String] asset_id Asset identifier
1273
+ # @param [String] version_id Specific version of asset to download
1274
+ # @param [Hash] options Additional options
1275
+ #
1276
+ # @return [String] An url for downloading a file
1277
+ def self.download_backedup_asset(asset_id, version_id, options = {})
1278
+ params = Cloudinary::Utils.sign_request({
1279
+ :timestamp => (options[:timestamp] || Time.now.to_i),
1280
+ :asset_id => asset_id,
1281
+ :version_id => version_id
1282
+ }, options)
1283
+
1284
+ "#{Cloudinary::Utils.base_api_url("download_backup", options)}?#{Cloudinary::Utils.hash_query_params((params))}"
1285
+ end
1286
+
1287
+ # Format date in a format accepted by the usage API (e.g., 31-12-2020) if
1288
+ # passed value is of type Date, otherwise return the string representation of
1289
+ # the input.
1290
+ #
1291
+ # @param [Date|Object] date
1292
+ # @return [String]
1293
+ def self.to_usage_api_date_format(date)
1294
+ if date.is_a?(Date)
1295
+ date.strftime('%d-%m-%Y')
1296
+ else
1297
+ date.to_s
1298
+ end
1299
+ end
1300
+
1301
+ # Verifies the authenticity of an API response signature.
1302
+ #
1303
+ # @param [String] public_id he public id of the asset as returned in the API response
1304
+ # @param [Fixnum] version The version of the asset as returned in the API response
1305
+ # @param [String] signature Actual signature. Can be retrieved from the X-Cld-Signature header
1306
+ # @param [Symbol|nil] signature_algorithm Algorithm to use for computing hash
1307
+ # @param [Hash] options
1308
+ # @option options [String] :api_secret API secret, if not passed taken from global config
1309
+ #
1310
+ # @return [Boolean]
1311
+ def self.verify_api_response_signature(public_id, version, signature, signature_algorithm = nil, options = {})
1312
+ api_secret = options[:api_secret] || Cloudinary.config.api_secret || raise("Must supply api_secret")
1313
+
1314
+ parameters_to_sign = {
1315
+ :public_id => public_id,
1316
+ :version => version
1317
+ }
1318
+
1319
+ signature == api_sign_request(parameters_to_sign, api_secret, signature_algorithm)
1320
+ end
1321
+
1322
+ # Verifies the authenticity of a notification signature.
1323
+ #
1324
+ # @param [String] body JSON of the request's body
1325
+ # @param [Fixnum] timestamp Unix timestamp. Can be retrieved from the X-Cld-Timestamp header
1326
+ # @param [String] signature Actual signature. Can be retrieved from the X-Cld-Signature header
1327
+ # @param [Fixnum] valid_for The desired time in seconds for considering the request valid
1328
+ # @param [Symbol|nil] signature_algorithm Algorithm to use for computing hash
1329
+ # @param [Hash] options
1330
+ # @option options [String] :api_secret API secret, if not passed taken from global config
1331
+ #
1332
+ # @return [Boolean]
1333
+ def self.verify_notification_signature(body, timestamp, signature, valid_for = 7200, signature_algorithm = nil, options = {})
1334
+ api_secret = options[:api_secret] || Cloudinary.config.api_secret || raise("Must supply api_secret")
1335
+ raise("Body should be of String type") unless body.is_a?(String)
1336
+ # verify that signature is valid for the given timestamp
1337
+ return false if timestamp < (Time.now - valid_for).to_i
1338
+
1339
+ payload_hash = hash("#{body}#{timestamp}#{api_secret}", signature_algorithm, :hexdigest)
1340
+
1341
+ signature == payload_hash
1342
+ end
1343
+
1344
+ # Computes hash from input string using specified algorithm.
1345
+ #
1346
+ # @param [String] input String which to compute hash from
1347
+ # @param [Symbol|nil] signature_algorithm Algorithm to use for computing hash
1348
+ # @param [Symbol] hash_method Hash method applied to a signature algorithm (:digest or :hexdigest)
1349
+ #
1350
+ # @return [String] Computed hash value
1351
+ def self.hash(input, signature_algorithm = nil, hash_method = :digest)
1352
+ signature_algorithm ||= Cloudinary.config.signature_algorithm || ALGO_SHA1
1353
+ algorithm = ALGORITHM_SIGNATURE[signature_algorithm] || raise("Unsupported algorithm '#{signature_algorithm}'")
1354
+ algorithm.public_send(hash_method, input)
1168
1355
  end
1169
1356
 
1170
- private_class_method :compute_signature
1357
+ private_class_method :hash
1171
1358
  end