activestorage 6.1.6 → 7.0.0.alpha1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of activestorage might be problematic. Click here for more details.

Files changed (68) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +129 -268
  3. data/MIT-LICENSE +1 -1
  4. data/README.md +25 -11
  5. data/app/assets/javascripts/activestorage.esm.js +844 -0
  6. data/app/assets/javascripts/activestorage.js +257 -376
  7. data/app/controllers/active_storage/base_controller.rb +1 -10
  8. data/app/controllers/active_storage/blobs/proxy_controller.rb +14 -4
  9. data/app/controllers/active_storage/blobs/redirect_controller.rb +6 -4
  10. data/app/controllers/active_storage/representations/base_controller.rb +5 -1
  11. data/app/controllers/active_storage/representations/proxy_controller.rb +6 -4
  12. data/app/controllers/active_storage/representations/redirect_controller.rb +6 -4
  13. data/app/controllers/concerns/active_storage/set_blob.rb +6 -2
  14. data/app/controllers/concerns/active_storage/set_current.rb +3 -3
  15. data/app/controllers/concerns/active_storage/streaming.rb +65 -0
  16. data/app/javascript/activestorage/ujs.js +1 -1
  17. data/app/models/active_storage/attachment.rb +35 -2
  18. data/app/models/active_storage/blob/representable.rb +7 -5
  19. data/app/models/active_storage/blob.rb +26 -27
  20. data/app/models/active_storage/current.rb +12 -2
  21. data/app/models/active_storage/preview.rb +6 -4
  22. data/app/models/active_storage/record.rb +1 -1
  23. data/app/models/active_storage/variant.rb +6 -9
  24. data/app/models/active_storage/variant_record.rb +2 -0
  25. data/app/models/active_storage/variant_with_record.rb +9 -5
  26. data/app/models/active_storage/variation.rb +3 -3
  27. data/config/routes.rb +10 -10
  28. data/db/migrate/20170806125915_create_active_storage_tables.rb +29 -8
  29. data/db/update_migrate/20191206030411_create_active_storage_variant_records.rb +15 -2
  30. data/lib/active_storage/analyzer/audio_analyzer.rb +65 -0
  31. data/lib/active_storage/analyzer/image_analyzer/image_magick.rb +39 -0
  32. data/lib/active_storage/analyzer/image_analyzer/vips.rb +49 -0
  33. data/lib/active_storage/analyzer/image_analyzer.rb +2 -30
  34. data/lib/active_storage/analyzer/video_analyzer.rb +26 -11
  35. data/lib/active_storage/analyzer.rb +8 -4
  36. data/lib/active_storage/attached/changes/create_many.rb +7 -3
  37. data/lib/active_storage/attached/changes/create_one.rb +1 -1
  38. data/lib/active_storage/attached/changes/create_one_of_many.rb +1 -1
  39. data/lib/active_storage/attached/changes/delete_many.rb +1 -1
  40. data/lib/active_storage/attached/changes/delete_one.rb +1 -1
  41. data/lib/active_storage/attached/changes/detach_many.rb +18 -0
  42. data/lib/active_storage/attached/changes/detach_one.rb +24 -0
  43. data/lib/active_storage/attached/changes/purge_many.rb +27 -0
  44. data/lib/active_storage/attached/changes/purge_one.rb +27 -0
  45. data/lib/active_storage/attached/changes.rb +7 -1
  46. data/lib/active_storage/attached/many.rb +27 -15
  47. data/lib/active_storage/attached/model.rb +31 -5
  48. data/lib/active_storage/attached/one.rb +32 -27
  49. data/lib/active_storage/downloader.rb +2 -2
  50. data/lib/active_storage/engine.rb +28 -16
  51. data/lib/active_storage/fixture_set.rb +76 -0
  52. data/lib/active_storage/gem_version.rb +4 -4
  53. data/lib/active_storage/previewer/video_previewer.rb +0 -2
  54. data/lib/active_storage/previewer.rb +4 -4
  55. data/lib/active_storage/reflection.rb +12 -2
  56. data/lib/active_storage/service/azure_storage_service.rb +1 -1
  57. data/lib/active_storage/service/configurator.rb +1 -1
  58. data/lib/active_storage/service/disk_service.rb +13 -18
  59. data/lib/active_storage/service/gcs_service.rb +91 -7
  60. data/lib/active_storage/service/mirror_service.rb +1 -1
  61. data/lib/active_storage/service/registry.rb +1 -1
  62. data/lib/active_storage/service/s3_service.rb +4 -4
  63. data/lib/active_storage/service.rb +3 -3
  64. data/lib/active_storage/transformers/image_processing_transformer.rb +1 -66
  65. data/lib/active_storage/transformers/transformer.rb +1 -1
  66. data/lib/active_storage.rb +3 -292
  67. metadata +32 -24
  68. data/app/controllers/concerns/active_storage/set_headers.rb +0 -12
@@ -1,12 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  gem "google-cloud-storage", "~> 1.11"
4
+ require "google/apis/iamcredentials_v1"
4
5
  require "google/cloud/storage"
5
6
 
6
7
  module ActiveStorage
7
8
  # Wraps the Google Cloud Storage as an Active Storage service. See ActiveStorage::Service for the generic API
8
9
  # documentation that applies to all services.
9
10
  class Service::GCSService < Service
11
+ class MetadataServerError < ActiveStorage::Error; end
12
+ class MetadataServerNotFoundError < ActiveStorage::Error; end
13
+
10
14
  def initialize(public: false, **config)
11
15
  @config = config
12
16
  @public = public
@@ -19,7 +23,7 @@ module ActiveStorage
19
23
  # binary and attachment when the file's content type requires it. The only way to force them is to
20
24
  # store them as object's metadata.
21
25
  content_disposition = content_disposition_with(type: disposition, filename: filename) if disposition && filename
22
- bucket.create_file(io, key, md5: checksum, content_type: content_type, content_disposition: content_disposition)
26
+ bucket.create_file(io, key, md5: checksum, cache_control: @config[:cache_control], content_type: content_type, content_disposition: content_disposition)
23
27
  rescue Google::Cloud::InvalidArgumentError
24
28
  raise ActiveStorage::IntegrityError
25
29
  end
@@ -84,7 +88,31 @@ module ActiveStorage
84
88
 
85
89
  def url_for_direct_upload(key, expires_in:, checksum:, **)
86
90
  instrument :url, key: key do |payload|
87
- generated_url = bucket.signed_url key, method: "PUT", expires: expires_in, content_md5: checksum
91
+ headers = {}
92
+ version = :v2
93
+
94
+ if @config[:cache_control].present?
95
+ headers["Cache-Control"] = @config[:cache_control]
96
+ # v2 signing doesn't support non `x-goog-` headers. Only switch to v4 signing
97
+ # if necessary for back-compat; v4 limits the expiration of the URL to 7 days
98
+ # whereas v2 has no limit
99
+ version = :v4
100
+ end
101
+
102
+ args = {
103
+ content_md5: checksum,
104
+ expires: expires_in,
105
+ headers: headers,
106
+ method: "PUT",
107
+ version: version,
108
+ }
109
+
110
+ if @config[:iam]
111
+ args[:issuer] = issuer
112
+ args[:signer] = signer
113
+ end
114
+
115
+ generated_url = bucket.signed_url(key, **args)
88
116
 
89
117
  payload[:url] = generated_url
90
118
 
@@ -95,15 +123,31 @@ module ActiveStorage
95
123
  def headers_for_direct_upload(key, checksum:, filename: nil, disposition: nil, **)
96
124
  content_disposition = content_disposition_with(type: disposition, filename: filename) if filename
97
125
 
98
- { "Content-MD5" => checksum, "Content-Disposition" => content_disposition }
126
+ headers = { "Content-MD5" => checksum, "Content-Disposition" => content_disposition }
127
+
128
+ if @config[:cache_control].present?
129
+ headers["Cache-Control"] = @config[:cache_control]
130
+ end
131
+
132
+ headers
99
133
  end
100
134
 
101
135
  private
102
136
  def private_url(key, expires_in:, filename:, content_type:, disposition:, **)
103
- file_for(key).signed_url expires: expires_in, query: {
104
- "response-content-disposition" => content_disposition_with(type: disposition, filename: filename),
105
- "response-content-type" => content_type
137
+ args = {
138
+ expires: expires_in,
139
+ query: {
140
+ "response-content-disposition" => content_disposition_with(type: disposition, filename: filename),
141
+ "response-content-type" => content_type
142
+ }
106
143
  }
144
+
145
+ if @config[:iam]
146
+ args[:issuer] = issuer
147
+ args[:signer] = signer
148
+ end
149
+
150
+ file_for(key).signed_url(**args)
107
151
  end
108
152
 
109
153
  def public_url(key, **)
@@ -137,7 +181,47 @@ module ActiveStorage
137
181
  end
138
182
 
139
183
  def client
140
- @client ||= Google::Cloud::Storage.new(**config.except(:bucket))
184
+ @client ||= Google::Cloud::Storage.new(**config.except(:bucket, :cache_control, :iam, :gsa_email))
185
+ end
186
+
187
+ def issuer
188
+ @issuer ||= if @config[:gsa_email]
189
+ @config[:gsa_email]
190
+ else
191
+ uri = URI.parse("http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/email")
192
+ http = Net::HTTP.new(uri.host, uri.port)
193
+ request = Net::HTTP::Get.new(uri.request_uri)
194
+ request["Metadata-Flavor"] = "Google"
195
+
196
+ begin
197
+ response = http.request(request)
198
+ rescue SocketError
199
+ raise MetadataServerNotFoundError
200
+ end
201
+
202
+ if response.is_a?(Net::HTTPSuccess)
203
+ response.body
204
+ else
205
+ raise MetadataServerError
206
+ end
207
+ end
208
+ end
209
+
210
+ def signer
211
+ # https://googleapis.dev/ruby/google-cloud-storage/latest/Google/Cloud/Storage/Project.html#signed_url-instance_method
212
+ lambda do |string_to_sign|
213
+ iam_client = Google::Apis::IamcredentialsV1::IAMCredentialsService.new
214
+
215
+ scopes = ["https://www.googleapis.com/auth/iam"]
216
+ iam_client.authorization = Google::Auth.get_application_default(scopes)
217
+
218
+ request = Google::Apis::IamcredentialsV1::SignBlobRequest.new(
219
+ payload: string_to_sign
220
+ )
221
+ resource = "projects/-/serviceAccounts/#{issuer}"
222
+ response = iam_client.sign_service_account_blob(resource, request)
223
+ response.signed_blob
224
+ end
141
225
  end
142
226
  end
143
227
  end
@@ -17,7 +17,7 @@ module ActiveStorage
17
17
  :url_for_direct_upload, :headers_for_direct_upload, :path_for, to: :primary
18
18
 
19
19
  # Stitch together from named services.
20
- def self.build(primary:, mirrors:, name:, configurator:, **options) #:nodoc:
20
+ def self.build(primary:, mirrors:, name:, configurator:, **options) # :nodoc:
21
21
  new(
22
22
  primary: configurator.build(primary),
23
23
  mirrors: mirrors.collect { |mirror_name| configurator.build mirror_name }
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveStorage
4
- class Service::Registry #:nodoc:
4
+ class Service::Registry # :nodoc:
5
5
  def initialize(configurations)
6
6
  @configurations = configurations.deep_symbolize_keys
7
7
  @services = {}
@@ -96,14 +96,14 @@ module ActiveStorage
96
96
  end
97
97
 
98
98
  private
99
- def private_url(key, expires_in:, filename:, disposition:, content_type:, **)
99
+ def private_url(key, expires_in:, filename:, disposition:, content_type:, **client_opts)
100
100
  object_for(key).presigned_url :get, expires_in: expires_in.to_i,
101
101
  response_content_disposition: content_disposition_with(type: disposition, filename: filename),
102
- response_content_type: content_type
102
+ response_content_type: content_type, **client_opts
103
103
  end
104
104
 
105
- def public_url(key, **)
106
- object_for(key).public_url
105
+ def public_url(key, **client_opts)
106
+ object_for(key).public_url(**client_opts)
107
107
  end
108
108
 
109
109
 
@@ -35,8 +35,8 @@ module ActiveStorage
35
35
  # can configure the service to use like this:
36
36
  #
37
37
  # ActiveStorage::Blob.service = ActiveStorage::Service.configure(
38
- # :Disk,
39
- # root: Pathname("/foo/bar/storage")
38
+ # :local,
39
+ # { local: {service: "Disk", root: Pathname("/tmp/foo/storage") } }
40
40
  # )
41
41
  class Service
42
42
  extend ActiveSupport::Autoload
@@ -57,7 +57,7 @@ module ActiveStorage
57
57
  # Passes the configurator and all of the service's config as keyword args.
58
58
  #
59
59
  # See MirrorService for an example.
60
- def build(configurator:, name:, service: nil, **service_config) #:nodoc:
60
+ def build(configurator:, name:, service: nil, **service_config) # :nodoc:
61
61
  new(**service_config).tap do |service_instance|
62
62
  service_instance.name = name
63
63
  end
@@ -13,9 +13,6 @@ module ActiveStorage
13
13
  module Transformers
14
14
  class ImageProcessingTransformer < Transformer
15
15
  private
16
- class UnsupportedImageProcessingMethod < StandardError; end
17
- class UnsupportedImageProcessingArgument < StandardError; end
18
-
19
16
  def process(file, format:)
20
17
  processor.
21
18
  source(file).
@@ -31,14 +28,10 @@ module ActiveStorage
31
28
 
32
29
  def operations
33
30
  transformations.each_with_object([]) do |(name, argument), list|
34
- if ActiveStorage.variant_processor == :mini_magick
35
- validate_transformation(name, argument)
36
- end
37
-
38
31
  if name.to_s == "combine_options"
39
32
  raise ArgumentError, <<~ERROR.squish
40
33
  Active Storage's ImageProcessing transformer doesn't support :combine_options,
41
- as it always generates a single ImageMagick command.
34
+ as it always generates a single command.
42
35
  ERROR
43
36
  end
44
37
 
@@ -47,64 +40,6 @@ module ActiveStorage
47
40
  end
48
41
  end
49
42
  end
50
-
51
- def validate_transformation(name, argument)
52
- method_name = name.to_s.tr("-", "_")
53
-
54
- unless ActiveStorage.supported_image_processing_methods.any? { |method| method_name == method }
55
- raise UnsupportedImageProcessingMethod, <<~ERROR.squish
56
- One or more of the provided transformation methods is not supported.
57
- ERROR
58
- end
59
-
60
- if argument.present?
61
- if argument.is_a?(String) || argument.is_a?(Symbol)
62
- validate_arg_string(argument)
63
- elsif argument.is_a?(Array)
64
- validate_arg_array(argument)
65
- elsif argument.is_a?(Hash)
66
- validate_arg_hash(argument)
67
- end
68
- end
69
- end
70
-
71
- def validate_arg_string(argument)
72
- unsupported_arguments = ActiveStorage.unsupported_image_processing_arguments.any? do |bad_arg|
73
- argument.to_s.downcase.include?(bad_arg)
74
- end
75
-
76
- raise UnsupportedImageProcessingArgument if unsupported_arguments
77
- end
78
-
79
- def validate_arg_array(argument)
80
- argument.each do |arg|
81
- if arg.is_a?(Integer) || arg.is_a?(Float)
82
- next
83
- elsif arg.is_a?(String) || arg.is_a?(Symbol)
84
- validate_arg_string(arg)
85
- elsif arg.is_a?(Array)
86
- validate_arg_array(arg)
87
- elsif arg.is_a?(Hash)
88
- validate_arg_hash(arg)
89
- end
90
- end
91
- end
92
-
93
- def validate_arg_hash(argument)
94
- argument.each do |key, value|
95
- validate_arg_string(key)
96
-
97
- if value.is_a?(Integer) || value.is_a?(Float)
98
- next
99
- elsif value.is_a?(String) || value.is_a?(Symbol)
100
- validate_arg_string(value)
101
- elsif value.is_a?(Array)
102
- validate_arg_array(value)
103
- elsif value.is_a?(Hash)
104
- validate_arg_hash(value)
105
- end
106
- end
107
- end
108
43
  end
109
44
  end
110
45
  end
@@ -31,7 +31,7 @@ module ActiveStorage
31
31
  private
32
32
  # Returns an open Tempfile containing a transformed image in the given +format+.
33
33
  # All subclasses implement this method.
34
- def process(file, format:) #:doc:
34
+ def process(file, format:) # :doc:
35
35
  raise NotImplementedError
36
36
  end
37
37
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  #--
4
- # Copyright (c) 2017-2022 David Heinemeier Hansson, Basecamp
4
+ # Copyright (c) 2017-2021 David Heinemeier Hansson, Basecamp
5
5
  #
6
6
  # Permission is hereby granted, free of charge, to any person obtaining
7
7
  # a copy of this software and associated documentation files (the
@@ -37,6 +37,7 @@ module ActiveStorage
37
37
  extend ActiveSupport::Autoload
38
38
 
39
39
  autoload :Attached
40
+ autoload :FixtureSet
40
41
  autoload :Service
41
42
  autoload :Previewer
42
43
  autoload :Analyzer
@@ -58,298 +59,8 @@ module ActiveStorage
58
59
  mattr_accessor :content_types_to_serve_as_binary, default: []
59
60
  mattr_accessor :content_types_allowed_inline, default: []
60
61
 
61
- mattr_accessor :supported_image_processing_methods, default: [
62
- "adaptive_blur",
63
- "adaptive_resize",
64
- "adaptive_sharpen",
65
- "adjoin",
66
- "affine",
67
- "alpha",
68
- "annotate",
69
- "antialias",
70
- "append",
71
- "apply",
72
- "attenuate",
73
- "authenticate",
74
- "auto_gamma",
75
- "auto_level",
76
- "auto_orient",
77
- "auto_threshold",
78
- "backdrop",
79
- "background",
80
- "bench",
81
- "bias",
82
- "bilateral_blur",
83
- "black_point_compensation",
84
- "black_threshold",
85
- "blend",
86
- "blue_primary",
87
- "blue_shift",
88
- "blur",
89
- "border",
90
- "bordercolor",
91
- "borderwidth",
92
- "brightness_contrast",
93
- "cache",
94
- "canny",
95
- "caption",
96
- "channel",
97
- "channel_fx",
98
- "charcoal",
99
- "chop",
100
- "clahe",
101
- "clamp",
102
- "clip",
103
- "clip_path",
104
- "clone",
105
- "clut",
106
- "coalesce",
107
- "colorize",
108
- "colormap",
109
- "color_matrix",
110
- "colors",
111
- "colorspace",
112
- "colourspace",
113
- "color_threshold",
114
- "combine",
115
- "combine_options",
116
- "comment",
117
- "compare",
118
- "complex",
119
- "compose",
120
- "composite",
121
- "compress",
122
- "connected_components",
123
- "contrast",
124
- "contrast_stretch",
125
- "convert",
126
- "convolve",
127
- "copy",
128
- "crop",
129
- "cycle",
130
- "deconstruct",
131
- "define",
132
- "delay",
133
- "delete",
134
- "density",
135
- "depth",
136
- "descend",
137
- "deskew",
138
- "despeckle",
139
- "direction",
140
- "displace",
141
- "dispose",
142
- "dissimilarity_threshold",
143
- "dissolve",
144
- "distort",
145
- "dither",
146
- "draw",
147
- "duplicate",
148
- "edge",
149
- "emboss",
150
- "encoding",
151
- "endian",
152
- "enhance",
153
- "equalize",
154
- "evaluate",
155
- "evaluate_sequence",
156
- "extent",
157
- "extract",
158
- "family",
159
- "features",
160
- "fft",
161
- "fill",
162
- "filter",
163
- "flatten",
164
- "flip",
165
- "floodfill",
166
- "flop",
167
- "font",
168
- "foreground",
169
- "format",
170
- "frame",
171
- "function",
172
- "fuzz",
173
- "fx",
174
- "gamma",
175
- "gaussian_blur",
176
- "geometry",
177
- "gravity",
178
- "grayscale",
179
- "green_primary",
180
- "hald_clut",
181
- "highlight_color",
182
- "hough_lines",
183
- "iconGeometry",
184
- "iconic",
185
- "identify",
186
- "ift",
187
- "illuminant",
188
- "immutable",
189
- "implode",
190
- "insert",
191
- "intensity",
192
- "intent",
193
- "interlace",
194
- "interline_spacing",
195
- "interpolate",
196
- "interpolative_resize",
197
- "interword_spacing",
198
- "kerning",
199
- "kmeans",
200
- "kuwahara",
201
- "label",
202
- "lat",
203
- "layers",
204
- "level",
205
- "level_colors",
206
- "limit",
207
- "limits",
208
- "linear_stretch",
209
- "linewidth",
210
- "liquid_rescale",
211
- "list",
212
- "loader",
213
- "log",
214
- "loop",
215
- "lowlight_color",
216
- "magnify",
217
- "map",
218
- "mattecolor",
219
- "median",
220
- "mean_shift",
221
- "metric",
222
- "mode",
223
- "modulate",
224
- "moments",
225
- "monitor",
226
- "monochrome",
227
- "morph",
228
- "morphology",
229
- "mosaic",
230
- "motion_blur",
231
- "name",
232
- "negate",
233
- "noise",
234
- "normalize",
235
- "opaque",
236
- "ordered_dither",
237
- "orient",
238
- "page",
239
- "paint",
240
- "pause",
241
- "perceptible",
242
- "ping",
243
- "pointsize",
244
- "polaroid",
245
- "poly",
246
- "posterize",
247
- "precision",
248
- "preview",
249
- "process",
250
- "quality",
251
- "quantize",
252
- "quiet",
253
- "radial_blur",
254
- "raise",
255
- "random_threshold",
256
- "range_threshold",
257
- "red_primary",
258
- "regard_warnings",
259
- "region",
260
- "remote",
261
- "render",
262
- "repage",
263
- "resample",
264
- "resize",
265
- "resize_to_fill",
266
- "resize_to_fit",
267
- "resize_to_limit",
268
- "resize_and_pad",
269
- "respect_parentheses",
270
- "reverse",
271
- "roll",
272
- "rotate",
273
- "sample",
274
- "sampling_factor",
275
- "saver",
276
- "scale",
277
- "scene",
278
- "screen",
279
- "seed",
280
- "segment",
281
- "selective_blur",
282
- "separate",
283
- "sepia_tone",
284
- "shade",
285
- "shadow",
286
- "shared_memory",
287
- "sharpen",
288
- "shave",
289
- "shear",
290
- "sigmoidal_contrast",
291
- "silent",
292
- "similarity_threshold",
293
- "size",
294
- "sketch",
295
- "smush",
296
- "snaps",
297
- "solarize",
298
- "sort_pixels",
299
- "sparse_color",
300
- "splice",
301
- "spread",
302
- "statistic",
303
- "stegano",
304
- "stereo",
305
- "storage_type",
306
- "stretch",
307
- "strip",
308
- "stroke",
309
- "strokewidth",
310
- "style",
311
- "subimage_search",
312
- "swap",
313
- "swirl",
314
- "synchronize",
315
- "taint",
316
- "text_font",
317
- "threshold",
318
- "thumbnail",
319
- "tile_offset",
320
- "tint",
321
- "title",
322
- "transform",
323
- "transparent",
324
- "transparent_color",
325
- "transpose",
326
- "transverse",
327
- "treedepth",
328
- "trim",
329
- "type",
330
- "undercolor",
331
- "unique_colors",
332
- "units",
333
- "unsharp",
334
- "update",
335
- "valid_image",
336
- "view",
337
- "vignette",
338
- "virtual_pixel",
339
- "visual",
340
- "watermark",
341
- "wave",
342
- "wavelet_denoise",
343
- "weight",
344
- "white_balance",
345
- "white_point",
346
- "white_threshold",
347
- "window",
348
- "window_group"
349
- ]
350
- mattr_accessor :unsupported_image_processing_arguments
351
-
352
62
  mattr_accessor :service_urls_expire_in, default: 5.minutes
63
+ mattr_accessor :urls_expire_in
353
64
 
354
65
  mattr_accessor :routes_prefix, default: "/rails/active_storage"
355
66
  mattr_accessor :draw_routes, default: true