inline_forms 8.1.56 → 8.1.58

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.
Files changed (33) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +97 -0
  3. data/app/assets/stylesheets/inline_forms/_theme.scss +13 -0
  4. data/app/assets/stylesheets/inline_forms/devise.scss +4 -1
  5. data/app/assets/stylesheets/inline_forms/foundation_and_overrides.scss +1 -0
  6. data/app/assets/stylesheets/inline_forms/inline_forms.scss +35 -16
  7. data/app/controllers/inline_forms_controller.rb +50 -6
  8. data/app/helpers/inline_forms_file_trash_helper.rb +10 -0
  9. data/app/models/concerns/inline_forms/stored_files.rb +5 -1
  10. data/app/models/inline_forms/trashed_file.rb +16 -2
  11. data/app/views/devise/sessions/_form.html.erb +2 -2
  12. data/app/views/inline_forms/_list.html.erb +24 -7
  13. data/app/views/inline_forms/_versions.html.erb +1 -1
  14. data/app/views/inline_forms/_versions_file_entry.html.erb +1 -1
  15. data/app/views/inline_forms/_versions_list.html.erb +14 -12
  16. data/app/views/layouts/application.html.erb +10 -3
  17. data/app/views/layouts/devise.html.erb +1 -1
  18. data/app/views/layouts/inline_forms.html.erb +9 -2
  19. data/lib/inline_forms/form_elements/dropdown_helper.rb +1 -1
  20. data/lib/inline_forms/form_elements/file_field_helper.rb +3 -1
  21. data/lib/inline_forms/form_elements/info_list_helper.rb +3 -1
  22. data/lib/inline_forms/version.rb +1 -1
  23. data/lib/locales/inline_forms.en.yml +23 -1
  24. data/lib/locales/inline_forms.nl.yml +26 -1
  25. data/test/dummy/config/application.rb +11 -0
  26. data/test/integration/csp_nonce_test.rb +50 -0
  27. data/test/integration/dropdown_test.rb +17 -0
  28. data/test/integration/file_slots_test.rb +91 -0
  29. data/test/integration/list_params_test.rb +200 -0
  30. data/test/integration/locale_test.rb +131 -0
  31. data/test/integration/presentation_escaping_test.rb +80 -0
  32. metadata +11 -2
  33. data/app/views/inline_forms/_flash.html.erb +0 -13
@@ -0,0 +1,131 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../integration_test_helper"
4
+
5
+ # The gem's own strings resolve in en and nl, also under
6
+ # raise_on_missing_translations (StProject switched to :nl with :strict):
7
+ # the create flash (`success`, misspelled `succes` in both locale files), the
8
+ # versions panel (was hard-coded English), the nl view keys that were missing
9
+ # (restore, list_versions, close_versions_list, undelete), localized version
10
+ # timestamps, the Devise layout's lang and the sign-in button.
11
+ class LocaleTest < InlineFormsIntegrationTestCase
12
+ KEYS = %w[
13
+ success
14
+ inline_forms.view.restore inline_forms.view.list_versions inline_forms.view.close_versions_list
15
+ inline_forms.view.undelete inline_forms.view.versions inline_forms.view.versions_event
16
+ inline_forms.view.versions_when inline_forms.view.versions_done_by inline_forms.view.versions_changeset
17
+ inline_forms.view.versions_old_value inline_forms.view.versions_new_value inline_forms.view.versions_empty
18
+ inline_forms.view.versions_rich_text inline_forms.view.version_events.create
19
+ inline_forms.view.version_events.update inline_forms.view.version_events.destroy
20
+ inline_forms.devise.login common.more common.logout inline_forms.view.create_failed
21
+ ].freeze
22
+
23
+ setup do
24
+ @machine = Machine.create!(name: "Press")
25
+ @machine.update!(name: "Press 2")
26
+ end
27
+
28
+ teardown do
29
+ I18n.backend.reload!
30
+ end
31
+
32
+ # Every translation the request looks up must exist: a missing one raises,
33
+ # in the views and in the controller (create's flash).
34
+ def strictly(locale)
35
+ original_view = ActionView::Helpers::TranslationHelper.raise_on_missing_translations
36
+ original_handler = I18n.exception_handler
37
+ original_default = I18n.default_locale
38
+ ActionView::Helpers::TranslationHelper.raise_on_missing_translations = true
39
+ I18n.exception_handler = ->(exception, *) { raise exception.respond_to?(:to_exception) ? exception.to_exception : exception }
40
+ # with_locale first: it restores the locale it found, so the thread's
41
+ # locale must not be read after default_locale changed (it would then
42
+ # restore :nl for every later test).
43
+ I18n.with_locale(locale) do
44
+ I18n.default_locale = locale
45
+ yield
46
+ end
47
+ ensure
48
+ I18n.default_locale = original_default
49
+ ActionView::Helpers::TranslationHelper.raise_on_missing_translations = original_view
50
+ I18n.exception_handler = original_handler
51
+ end
52
+
53
+ test "the gem's keys exist in en and nl, and the misspelled succes is gone" do
54
+ %i[en nl].each do |locale|
55
+ KEYS.each { |key| assert I18n.exists?(key, locale), "#{locale}.#{key}" }
56
+ refute I18n.exists?("succes", locale), "#{locale}.succes"
57
+ end
58
+ assert_equal "Machine created.", I18n.t("success", message: "Machine", locale: :en)
59
+ assert_equal "Machine is aangemaakt.", I18n.t("success", message: "Machine", locale: :nl)
60
+ end
61
+
62
+ test "create's success flash translates in nl" do
63
+ # The model top bar shows t(controller_name): host-supplied, like
64
+ # StProject's `clients: cliënten`.
65
+ I18n.backend.store_translations(:nl, machines: "machines")
66
+ strictly(:nl) do
67
+ post machines_path(update: "machines_list"), params: { name: "Lathe" }, headers: frame_headers("machines_list")
68
+ end
69
+
70
+ assert_response :success
71
+ assert_equal "Machine is aangemaakt.", controller.flash[:success]
72
+ end
73
+
74
+ # The failure header was hard-coded Dutch with the underscored class name
75
+ # ("Kan machine niet aanmaken.") in every locale.
76
+ test "create's failure flash header translates, with the human model name" do
77
+ I18n.backend.store_translations(:nl, machines: "machines",
78
+ activerecord: { models: { machine: "Werkbank" } })
79
+ strictly(:nl) do
80
+ post machines_path(update: "machines_list"), params: { name: "" }, headers: frame_headers("machines_list")
81
+ end
82
+ assert_equal [ "Kan Werkbank niet aanmaken." ], controller.flash[:header]
83
+
84
+ I18n.backend.store_translations(:en, machines: "machines")
85
+ strictly(:en) do
86
+ post machines_path(update: "machines_list"), params: { name: "" }, headers: frame_headers("machines_list")
87
+ end
88
+ assert_equal [ "Could not create Machine." ], controller.flash[:header]
89
+ assert_includes response.body, "Could not create Machine."
90
+ end
91
+
92
+ test "the versions panel and list are translated, with localized timestamps" do
93
+ row = "machine_#{@machine.id}"
94
+ strictly(:nl) do
95
+ get machine_path(@machine, update: row), headers: frame_headers(row)
96
+ assert_response :success
97
+ assert_includes response.body, "Versies (2)"
98
+
99
+ get list_versions_machine_path(@machine, update: row), headers: frame_headers(row)
100
+ end
101
+
102
+ assert_response :success
103
+ body = response.body
104
+ %w[Versies Gebeurtenis Wanneer Door Wijzigingen aangemaakt gewijzigd terugzetten].each do |text|
105
+ assert_includes body, text
106
+ end
107
+ refute_includes body, "<strong>Event</strong>"
108
+ version = @machine.versions.last
109
+ assert_includes body, ERB::Util.h(I18n.l(version.created_at, format: :short, locale: :nl))
110
+ end
111
+
112
+ test "the versions list in en keeps its English labels" do
113
+ row = "machine_#{@machine.id}"
114
+ strictly(:en) do
115
+ get list_versions_machine_path(@machine, update: row), headers: frame_headers(row)
116
+ end
117
+
118
+ assert_response :success
119
+ assert_includes response.body, "Versions (2)"
120
+ assert_includes response.body, "<strong>Event</strong>"
121
+ assert_includes response.body, "old value"
122
+ end
123
+
124
+ test "the devise and application layouts declare the page language" do
125
+ get widgets_path
126
+ I18n.with_locale(:nl) do
127
+ assert_includes controller.render_to_string(template: "stats/show", layout: "devise"), %(<html lang="nl">)
128
+ assert_includes controller.render_to_string(template: "stats/show", layout: "application"), %(<html lang="nl">)
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../integration_test_helper"
4
+
5
+ # Model data that a helper concatenates into a string it marks html_safe must
6
+ # be escaped first. info_list_show did `out << item._presentation`, so a
7
+ # _presentation built from user input (StProject: User#name listed on a Role)
8
+ # ran as markup on every page that showed the list (stored XSS). Same audit:
9
+ # file_field_show (stored file name / url) and the rich-text changeset in the
10
+ # versions list (was `raw`).
11
+ class PresentationEscapingTest < InlineFormsIntegrationTestCase
12
+ MARKUP = "<img src=x onerror=alert(1)>"
13
+ ESCAPED = "&lt;img src=x onerror=alert(1)&gt;"
14
+
15
+ # A view context of a real request: helpers, can?, url helpers.
16
+ def view
17
+ @view ||= begin
18
+ get widgets_path
19
+ controller.view_context
20
+ end
21
+ end
22
+
23
+ test "info_list escapes each item's _presentation" do
24
+ machine = Machine.create!(name: "Press")
25
+ Part.create!(name: MARKUP, machine: machine)
26
+ Part.create!(name: "Gear", machine: machine)
27
+
28
+ html = view.info_list_show(machine, :parts)
29
+
30
+ refute_includes html, "<img"
31
+ assert_includes html, ESCAPED
32
+ assert_includes html, "Gear"
33
+ assert_includes html, "<div class='row", "the row markup itself is kept"
34
+ end
35
+
36
+ test "file_field escapes the stored file name and url" do
37
+ widget = Widget.create!(name: "Manual")
38
+ stored = Struct.new(:url, :path) do
39
+ def present? = true
40
+ def to_s = url
41
+ end.new(%(/uploads/x'><img src=x onerror=alert(1)>.pdf), %(/uploads/#{MARKUP}.pdf))
42
+ widget.define_singleton_method(:manual) { stored }
43
+
44
+ html = view.file_field_show(widget, :manual)
45
+
46
+ refute_includes html, "<img"
47
+ assert_includes html, ESCAPED
48
+ end
49
+
50
+ test "dropdown_with_other and the list rows escape _presentation" do
51
+ kind = Kind.create!(name: MARKUP)
52
+ widget = Widget.create!(name: MARKUP, kind: kind)
53
+
54
+ refute_includes view.dropdown_with_other_show(widget, :kind), "<img"
55
+
56
+ get widgets_path(update: "widgets_list"), headers: frame_headers("widgets_list")
57
+ assert_response :success
58
+ refute_includes response.body, "<img"
59
+ assert_includes response.body, ESCAPED
60
+ end
61
+
62
+ test "a rich-text changeset in the versions list is sanitized, not raw" do
63
+ machine = Machine.create!(name: "Press")
64
+ version = Struct.new(:id, :event, :changeset, :created_at, :whodunnit) do
65
+ def to_param = id.to_s
66
+ end.new(1, "update", { "body" => [ "<p>old</p><script>alert(1)</script>", "<p>new</p>#{MARKUP}" ] }, Time.current, nil)
67
+ context = view
68
+ context.instance_variable_set(:@object, machine)
69
+ context.define_singleton_method(:inline_forms_history_for) do |_object|
70
+ [ { version: version, kind: :rich_text, rich_text_name: "description", at: version.created_at } ]
71
+ end
72
+
73
+ html = context.render(partial: "inline_forms/versions_list")
74
+
75
+ refute_includes html, "<script"
76
+ refute_includes html, "onerror"
77
+ assert_includes html, "<p>old</p>"
78
+ assert_includes html, "<p>new</p>"
79
+ end
80
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: inline_forms
3
3
  version: !ruby/object:Gem::Version
4
- version: 8.1.56
4
+ version: 8.1.58
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ace Suares
@@ -153,7 +153,6 @@ files:
153
153
  - app/views/inline_forms/_close.html.erb
154
154
  - app/views/inline_forms/_edit.html.erb
155
155
  - app/views/inline_forms/_file_slot.html.erb
156
- - app/views/inline_forms/_flash.html.erb
157
156
  - app/views/inline_forms/_generic_search.html.erb
158
157
  - app/views/inline_forms/_header.html.erb
159
158
  - app/views/inline_forms/_list.html.erb
@@ -314,16 +313,21 @@ files:
314
313
  - test/inline_edit_polymorphic_path_test.rb
315
314
  - test/inline_forms_addto_generator_test.rb
316
315
  - test/inline_forms_generator_test.rb
316
+ - test/integration/csp_nonce_test.rb
317
+ - test/integration/dropdown_test.rb
317
318
  - test/integration/field_authorization_test.rb
318
319
  - test/integration/file_slots_test.rb
319
320
  - test/integration/html_refusal_test.rb
320
321
  - test/integration/inline_forms_crud_test.rb
321
322
  - test/integration/layout_title_test.rb
322
323
  - test/integration/legacy_elements_test.rb
324
+ - test/integration/list_params_test.rb
325
+ - test/integration/locale_test.rb
323
326
  - test/integration/model_top_bar_test.rb
324
327
  - test/integration/native_inputs_test.rb
325
328
  - test/integration/open_after_create_test.rb
326
329
  - test/integration/pending_migration_gate_test.rb
330
+ - test/integration/presentation_escaping_test.rb
327
331
  - test/integration/revert_skipped_columns_test.rb
328
332
  - test/integration/schema_batch_pipeline_test.rb
329
333
  - test/integration/schema_edit_test.rb
@@ -398,16 +402,21 @@ test_files:
398
402
  - test/inline_edit_polymorphic_path_test.rb
399
403
  - test/inline_forms_addto_generator_test.rb
400
404
  - test/inline_forms_generator_test.rb
405
+ - test/integration/csp_nonce_test.rb
406
+ - test/integration/dropdown_test.rb
401
407
  - test/integration/field_authorization_test.rb
402
408
  - test/integration/file_slots_test.rb
403
409
  - test/integration/html_refusal_test.rb
404
410
  - test/integration/inline_forms_crud_test.rb
405
411
  - test/integration/layout_title_test.rb
406
412
  - test/integration/legacy_elements_test.rb
413
+ - test/integration/list_params_test.rb
414
+ - test/integration/locale_test.rb
407
415
  - test/integration/model_top_bar_test.rb
408
416
  - test/integration/native_inputs_test.rb
409
417
  - test/integration/open_after_create_test.rb
410
418
  - test/integration/pending_migration_gate_test.rb
419
+ - test/integration/presentation_escaping_test.rb
411
420
  - test/integration/revert_skipped_columns_test.rb
412
421
  - test/integration/schema_batch_pipeline_test.rb
413
422
  - test/integration/schema_edit_test.rb
@@ -1,13 +0,0 @@
1
- <% unless flash.empty? %>
2
- <% identifier = Time.now.to_i.to_s %>
3
- <div class="row" id="flash_<%= identifier %>">
4
- <% flash.each do |key, value| %>
5
- <%= value %>
6
- <% end %>
7
- <script>
8
- $("#<%= "#{update_span}_auto_header" %>").ScrollTo();
9
- $("#flash_<%= identifier %>").delay(1000).fadeToggle('2000');
10
- </script>
11
- </div>
12
- <% end %>
13
-