decidim-core 0.31.7 → 0.31.8

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 (119) hide show
  1. checksums.yaml +4 -4
  2. data/app/cells/decidim/content_blocks/participatory_space_main_data/title.erb +1 -14
  3. data/app/cells/decidim/following_cell.rb +1 -1
  4. data/app/cells/decidim/nav_links/show.erb +3 -1
  5. data/app/cells/decidim/participatory_space_private_user/show.erb +1 -1
  6. data/app/cells/decidim/share_text_widget/modal.erb +1 -0
  7. data/app/cells/decidim/share_widget/modal.erb +1 -0
  8. data/app/cells/decidim/share_widget/qr_code_modal.erb +2 -4
  9. data/app/commands/decidim/create_omniauth_registration.rb +17 -2
  10. data/app/controllers/concerns/decidim/devise_authentication_methods.rb +4 -0
  11. data/app/controllers/concerns/decidim/devise_controllers.rb +2 -0
  12. data/app/controllers/concerns/decidim/disable_redirection_to_external_host.rb +2 -0
  13. data/app/controllers/concerns/decidim/filter_resource.rb +2 -0
  14. data/app/controllers/concerns/decidim/impersonate_users.rb +2 -2
  15. data/app/controllers/concerns/decidim/locale_switcher.rb +62 -11
  16. data/app/controllers/concerns/decidim/needs_permission.rb +22 -10
  17. data/app/controllers/concerns/decidim/onboarding_action_methods.rb +2 -0
  18. data/app/controllers/concerns/decidim/paginable.rb +7 -3
  19. data/app/controllers/concerns/decidim/payload_info.rb +2 -0
  20. data/app/controllers/concerns/decidim/resource_versions_concern.rb +14 -0
  21. data/app/controllers/decidim/application_controller.rb +0 -6
  22. data/app/controllers/decidim/authorization_modals_controller.rb +2 -0
  23. data/app/controllers/decidim/components/base_controller.rb +2 -0
  24. data/app/controllers/decidim/devise/passwords_controller.rb +8 -0
  25. data/app/controllers/decidim/favicon_controller.rb +9 -0
  26. data/app/controllers/decidim/free_resource_authorization_modals_controller.rb +2 -0
  27. data/app/controllers/decidim/gamification/badges_controller.rb +2 -0
  28. data/app/controllers/decidim/homepage_controller.rb +4 -1
  29. data/app/controllers/decidim/likes_controller.rb +1 -1
  30. data/app/controllers/decidim/links_controller.rb +2 -0
  31. data/app/controllers/decidim/manifests_controller.rb +2 -0
  32. data/app/controllers/decidim/messaging/conversations_controller.rb +10 -4
  33. data/app/controllers/decidim/newsletters_controller.rb +4 -0
  34. data/app/controllers/decidim/notifications_subscriptions_controller.rb +4 -0
  35. data/app/controllers/decidim/offline_controller.rb +5 -1
  36. data/app/controllers/decidim/open_data_controller.rb +5 -1
  37. data/app/controllers/decidim/pages_controller.rb +31 -3
  38. data/app/controllers/decidim/private_downloads_controller.rb +2 -0
  39. data/app/controllers/decidim/profiles_controller.rb +10 -0
  40. data/app/controllers/decidim/qr_controller.rb +3 -1
  41. data/app/controllers/decidim/resource_autocomplete_controller.rb +2 -0
  42. data/app/controllers/decidim/searches_controller.rb +2 -0
  43. data/app/controllers/decidim/short_links_controller.rb +4 -0
  44. data/app/controllers/decidim/static_map_controller.rb +2 -0
  45. data/app/controllers/decidim/timeouts_controller.rb +4 -0
  46. data/app/controllers/decidim/tos_controller.rb +2 -0
  47. data/app/controllers/decidim/upload_validations_controller.rb +4 -2
  48. data/app/controllers/decidim/user_activities_controller.rb +2 -0
  49. data/app/forms/decidim/upload_validation_form.rb +16 -10
  50. data/app/helpers/concerns/decidim/user_role_checker.rb +35 -10
  51. data/app/helpers/decidim/sanitize_helper.rb +11 -1
  52. data/app/jobs/decidim/application_job.rb +4 -0
  53. data/app/models/decidim/authorization.rb +24 -0
  54. data/app/models/decidim/oauth_application.rb +1 -0
  55. data/app/models/decidim/user.rb +1 -0
  56. data/app/models/decidim/user_base_entity.rb +1 -1
  57. data/app/packs/src/decidim/callout.js +10 -0
  58. data/app/packs/src/decidim/callout.test.js +32 -0
  59. data/app/packs/src/decidim/controllers/accordion/accordion.test.js +64 -0
  60. data/app/packs/src/decidim/controllers/accordion/controller.js +43 -0
  61. data/app/packs/src/decidim/datepicker/datepicker_functions.js +34 -23
  62. data/app/packs/src/decidim/datepicker/generate_datepicker.js +4 -4
  63. data/app/packs/src/decidim/datepicker/test/time.test.js +39 -24
  64. data/app/packs/src/decidim/utilities/dom.js +2 -2
  65. data/app/packs/stylesheets/decidim/_content_blocks.scss +4 -10
  66. data/app/packs/stylesheets/decidim/_conversations.scss +0 -8
  67. data/app/packs/stylesheets/decidim/_modal.scss +16 -8
  68. data/app/packs/stylesheets/decidim/_modal_share.scss +0 -22
  69. data/app/packs/stylesheets/decidim/_participatory_spaces.scss +11 -1
  70. data/app/packs/stylesheets/decidim/_rich_text.scss +12 -9
  71. data/app/packs/stylesheets/decidim/_typography.scss +5 -2
  72. data/app/packs/stylesheets/decidim/legacy/email.scss +0 -5
  73. data/app/permissions/decidim/permissions.rb +20 -0
  74. data/app/presenters/decidim/admin_log/component_presenter.rb +1 -1
  75. data/app/presenters/decidim/log/base_presenter.rb +10 -1
  76. data/app/presenters/decidim/log/user_presenter.rb +2 -2
  77. data/app/serializers/decidim/exporters/open_data_blocked_user_serializer.rb +11 -2
  78. data/app/serializers/decidim/exporters/open_data_moderation_serializer.rb +25 -1
  79. data/app/services/decidim/svg_content_validator.rb +554 -0
  80. data/app/uploaders/decidim/application_uploader.rb +2 -2
  81. data/app/validators/uploader_image_content_validator.rb +278 -0
  82. data/app/views/decidim/application/_radio_accordion.html.erb +1 -5
  83. data/app/views/decidim/messaging/conversations/create.js.erb +1 -3
  84. data/app/views/decidim/shared/_login_modal.html.erb +12 -14
  85. data/app/views/layouts/decidim/_mailer_logo.html.erb +1 -1
  86. data/config/initializers/rack_attack.rb +10 -0
  87. data/config/locales/bg.yml +0 -1
  88. data/config/locales/ca-IT.yml +3 -1
  89. data/config/locales/ca.yml +3 -1
  90. data/config/locales/cs.yml +7 -1
  91. data/config/locales/de.yml +61 -3
  92. data/config/locales/en.yml +3 -1
  93. data/config/locales/es-MX.yml +3 -1
  94. data/config/locales/es-PY.yml +3 -1
  95. data/config/locales/es.yml +7 -5
  96. data/config/locales/eu.yml +155 -156
  97. data/config/locales/fi-plain.yml +1 -1
  98. data/config/locales/fi.yml +1 -1
  99. data/config/locales/fr-CA.yml +3 -1
  100. data/config/locales/fr.yml +3 -1
  101. data/config/locales/gl.yml +1441 -33
  102. data/config/locales/hu.yml +0 -1
  103. data/config/locales/ja.yml +1 -1
  104. data/config/locales/pl.yml +0 -1
  105. data/config/locales/pt-BR.yml +0 -1
  106. data/config/locales/ro-RO.yml +0 -1
  107. data/config/locales/sk.yml +2 -1
  108. data/config/locales/sv.yml +47 -10
  109. data/config/locales/tr-TR.yml +0 -2
  110. data/db/migrate/20260909000000_add_failed_attempts_and_locked_at_to_decidim_authorizations.rb +8 -0
  111. data/lib/decidim/core/test/shared_examples/participatory_space_members_page_examples.rb +77 -0
  112. data/lib/decidim/core/test/shared_examples/participatory_space_members_shared_examples.rb +85 -6
  113. data/lib/decidim/core/test/shared_examples/versions_controller_examples.rb +23 -1
  114. data/lib/decidim/core/version.rb +1 -1
  115. data/lib/decidim/core.rb +9 -0
  116. data/lib/decidim/has_upload_validations.rb +1 -0
  117. data/lib/decidim/legacy_form_builder.rb +4 -0
  118. data/lib/decidim/query_extensions.rb +2 -0
  119. metadata +10 -6
@@ -0,0 +1,278 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This validator ensures that files declared as images actually contain valid
4
+ # image data of the declared format. It does so by checking the leading bytes
5
+ # (magic bytes / file signature) of the file against the known signatures of
6
+ # the supported image formats, and by comparing the detected format with the
7
+ # content type declared by the file.
8
+ #
9
+ # This prevents non-image files (e.g. HTML, MATLAB, or arbitrary binary data)
10
+ # from being uploaded with an image extension and a spoofed image content type.
11
+ # Such files would later cause image processing to fail (e.g. in ImageMagick or
12
+ # in libvips when generating a variant) and could lead to a denial of service.
13
+ #
14
+ # Comparing the detected format with the declared content type also prevents
15
+ # files whose contents do not match their declaration (e.g. a JPEG file
16
+ # declared as image/png), so that the stored MIME type is consistent with the
17
+ # file contents.
18
+ #
19
+ # SVG documents are XML-based and can contain scripts and other active content,
20
+ # which is executed when the document is rendered by the browser. Because of
21
+ # that, the contents of files detected as SVG are additionally validated by
22
+ # Decidim::SvgContentValidator, so that documents which could execute scripts
23
+ # or read external resources are rejected instead of being stored as they are.
24
+ class UploaderImageContentValidator < ActiveModel::Validations::FileContentTypeValidator
25
+ # The number of leading bytes to read from the file to detect its signature.
26
+ SIGNATURE_LENGTH = 128
27
+
28
+ # The known signatures of the supported binary image formats. Each entry
29
+ # associates a format with a list of [offset, bytes] pairs that must all
30
+ # match at their respective offsets for the file to be considered of that
31
+ # format.
32
+ BINARY_IMAGE_SIGNATURES = [
33
+ [:png, [[0, "\x89PNG\r\n\x1A\n".b]]],
34
+ [:jpeg, [[0, "\xFF\xD8\xFF".b]]],
35
+ [:gif, [[0, "GIF87a".b]]],
36
+ [:gif, [[0, "GIF89a".b]]],
37
+ [:bmp, [[0, "BM".b]]],
38
+ [:tiff, [[0, "II*\x00".b]]],
39
+ [:tiff, [[0, "MM\x00*".b]]],
40
+ [:webp, [[0, "RIFF".b], [8, "WEBP".b]]],
41
+ [:ico, [[0, "\x00\x00\x01\x00".b]]]
42
+ ].freeze
43
+
44
+ # The major brands of the ISOBMFF "ftyp" box that identify HEIF and AVIF
45
+ # images. These formats share the same leading layout as other "ftyp" files
46
+ # (e.g. MP4 video), so the brand at offset 8 is used to tell images apart.
47
+ # The brands are grouped per format family, because HEIF files may use any
48
+ # of the HEIF brands regardless of their extension (e.g. a ".heic" file
49
+ # with the "mif1" major brand).
50
+ FTYPE_IMAGE_BRANDS = {
51
+ heif: %w(heic heix hevc hevx mif1 msf1).map(&:b).freeze,
52
+ avif: %w(avif avis).map(&:b).freeze
53
+ }.freeze
54
+
55
+ # The leading markup of an SVG document, which is XML-based and therefore has
56
+ # no fixed binary signature.
57
+ SVG_SIGNATURES = ["<svg".b, "<?xml".b, "<!DOCTYPE svg".b].freeze
58
+
59
+ # The content types accepted for each image format detected from the file
60
+ # signature. The content type declared by the file must be one of these, so
61
+ # that the stored MIME type is consistent with the file contents. The lists
62
+ # include the aliases commonly used in the wild for the same format.
63
+ FORMAT_CONTENT_TYPES = {
64
+ png: %w(image/png),
65
+ jpeg: %w(image/jpeg image/jpg image/pjpeg),
66
+ gif: %w(image/gif),
67
+ bmp: %w(image/bmp image/x-ms-bmp image/windows-bmp),
68
+ tiff: %w(image/tiff image/x-tiff),
69
+ webp: %w(image/webp),
70
+ ico: %w(image/vnd.microsoft.icon image/x-icon image/ico image/icon),
71
+ heif: %w(image/heic image/heif image/heic-sequence image/heif-sequence),
72
+ avif: %w(image/avif image/avis),
73
+ svg: %w(image/svg+xml)
74
+ }.freeze
75
+
76
+ def validate_each(record, attribute, value)
77
+ begin
78
+ values = parse_values(value)
79
+ rescue JSON::ParserError
80
+ record.errors.add attribute, :invalid
81
+ return
82
+ end
83
+
84
+ return if values.empty?
85
+
86
+ values.each do |val|
87
+ validate_image_content(record, attribute, val)
88
+ end
89
+ end
90
+
91
+ def check_validity!; end
92
+
93
+ private
94
+
95
+ def validate_image_content(record, attribute, file)
96
+ content_type = normalize_content_type(declared_content_type(file))
97
+ return unless content_type.start_with?("image/")
98
+
99
+ signature = file_signature(file)
100
+ return if signature.nil?
101
+
102
+ detected = image_format(signature)
103
+ return add_invalid_image(record, attribute) unless valid_image_format?(detected, content_type)
104
+
105
+ validate_svg_content(record, attribute, file) if detected == :svg
106
+ end
107
+
108
+ # The content type declared by the file, either through the blob of an
109
+ # attached file or through the upload itself.
110
+ def declared_content_type(file)
111
+ if file.is_a?(ActiveStorage::Attached)
112
+ file.blob&.content_type
113
+ else
114
+ file.try(:content_type)
115
+ end
116
+ end
117
+
118
+ # Strips the content type parameters (e.g. "image/png;charset=binary") and
119
+ # normalizes the casing, as content types are case insensitive.
120
+ def normalize_content_type(content_type)
121
+ content_type.to_s.split(";", 2).first.to_s.strip.downcase
122
+ end
123
+
124
+ # Whether the format detected from the file signature is one of the
125
+ # supported image formats and is consistent with the content type declared
126
+ # by the file.
127
+ def valid_image_format?(detected, content_type)
128
+ return false if detected.nil?
129
+
130
+ FORMAT_CONTENT_TYPES.fetch(detected).include?(content_type)
131
+ end
132
+
133
+ # Validates the contents of a file detected as SVG, which can contain active
134
+ # content. The validation is delegated to Decidim::SvgContentValidator, so
135
+ # that the SVG specific logic is kept out of this validator. Documents which
136
+ # are not well formed SVG documents are rejected as invalid images, and the
137
+ # ones which could execute scripts or read external resources when they are
138
+ # rendered are rejected as unsafe files.
139
+ def validate_svg_content(record, attribute, file)
140
+ case Decidim::SvgContentValidator.validate(file_content(file))
141
+ when :invalid
142
+ add_invalid_image(record, attribute)
143
+ when :unsafe
144
+ add_unsafe_svg(record, attribute)
145
+ end
146
+ end
147
+
148
+ def add_invalid_image(record, attribute)
149
+ record.errors.add attribute, I18n.t("decidim.errors.files.file_is_not_a_valid_image")
150
+ end
151
+
152
+ def add_unsafe_svg(record, attribute)
153
+ record.errors.add attribute, I18n.t("decidim.errors.files.file_contains_unsafe_content")
154
+ end
155
+
156
+ # Reads the leading bytes (signature) of the file. Returns nil when the file
157
+ # cannot be read, in which case the validation is skipped. Empty files yield
158
+ # an empty signature, which is rejected.
159
+ def file_signature(file)
160
+ read_file(file, SIGNATURE_LENGTH)
161
+ end
162
+
163
+ # Reads the whole contents of the file. Returns nil when the file cannot be
164
+ # read.
165
+ def file_content(file)
166
+ read_file(file)
167
+ end
168
+
169
+ # Reads the contents of the file, or only its leading bytes when a maximum
170
+ # length is given.
171
+ def read_file(file, max_length = nil)
172
+ if uploaded_file?(file)
173
+ File.open(file.path, "rb") { |io| read_io(io, max_length) }
174
+ elsif file.is_a?(ActiveStorage::Attached)
175
+ read_attached(file, max_length)
176
+ end
177
+ rescue ActiveStorage::Error, Errno::ENOENT, IOError
178
+ nil
179
+ end
180
+
181
+ # Reads the contents of an IO, or only its leading bytes when a maximum
182
+ # length is given. IO#read returns nil at the end of the file, so an empty
183
+ # file would otherwise be indistinguishable from an unreadable one and skip
184
+ # the validation. Normalizing it to an empty content keeps empty files
185
+ # rejected, while nil is kept for files which cannot be read at all.
186
+ def read_io(io, max_length = nil)
187
+ (max_length ? io.read(max_length) : io.read) || ""
188
+ end
189
+
190
+ # Reads the contents of an attached file. Blobs are only uploaded once the
191
+ # record is saved, so for still unpersisted blobs (e.g. an image assigned to
192
+ # a new record) the bytes are read from the pending attachable. Otherwise a
193
+ # spoofed image could be saved without being checked.
194
+ def read_attached(attached, max_length = nil)
195
+ blob = attached.blob
196
+ return read_blob(blob, max_length) if blob&.persisted?
197
+
198
+ read_pending_attachable(attached, max_length)
199
+ end
200
+
201
+ # ActiveStorage keeps the attachable of a pending attachment in the record's
202
+ # attachment changes until the blob is uploaded when the record is saved, so
203
+ # its contents can already be read during the validation.
204
+ def read_pending_attachable(attached, max_length = nil)
205
+ attachable = pending_attachable(attached)
206
+ if uploaded_file?(attachable) || attachable.is_a?(File)
207
+ File.open(attachable.path, "rb") { |io| read_io(io, max_length) }
208
+ elsif attachable.is_a?(Pathname)
209
+ File.open(attachable.to_path, "rb") { |io| read_io(io, max_length) }
210
+ elsif attachable.is_a?(Hash)
211
+ read_io_without_consuming(attachable[:io], max_length)
212
+ end
213
+ end
214
+
215
+ def pending_attachable(attached)
216
+ change = attached.record.try(:attachment_changes)&.[](attached.name.to_s)
217
+ change.try(:attachable)
218
+ end
219
+
220
+ # Reads the contents without consuming the IO, so ActiveStorage can still
221
+ # upload it after the validation.
222
+ def read_io_without_consuming(io, max_length = nil)
223
+ return unless io.respond_to?(:read) && io.respond_to?(:rewind)
224
+
225
+ io.rewind
226
+ content = read_io(io, max_length)
227
+ io.rewind
228
+ content
229
+ end
230
+
231
+ # Reads the whole contents of the blob, or only its leading bytes when a
232
+ # maximum length is given to keep the operation cheap for large files. The
233
+ # services return nil when the blob is empty, which is normalized to an
234
+ # empty content so that empty blobs are rejected. Falls back to opening the
235
+ # whole blob when the service does not support ranged downloads.
236
+ def read_blob(blob, max_length = nil)
237
+ return blob.download if max_length.nil?
238
+
239
+ blob.download_chunk(0...max_length) || ""
240
+ rescue NotImplementedError
241
+ blob.open { |io| read_io(io, max_length) }
242
+ end
243
+
244
+ # The image format detected from the file signature, or nil when the
245
+ # signature does not match any of the supported image formats.
246
+ def image_format(signature)
247
+ return nil if signature.blank?
248
+
249
+ binary_image_format(signature) || ftyp_image_format(signature) || svg_format(signature)
250
+ end
251
+
252
+ def binary_image_format(signature)
253
+ BINARY_IMAGE_SIGNATURES.find do |_format, pairs|
254
+ pairs.all? { |offset, bytes| signature[offset, bytes.bytesize] == bytes }
255
+ end&.first
256
+ end
257
+
258
+ # The format of an ISOBMFF-based image (HEIF or AVIF). These files start with
259
+ # a "ftyp" box at offset 4 followed by a major brand at offset 8.
260
+ def ftyp_image_format(signature)
261
+ return nil unless signature[4, 4] == "ftyp".b
262
+
263
+ FTYPE_IMAGE_BRANDS.find { |_format, brands| brands.include?(signature[8, 4]) }&.first
264
+ end
265
+
266
+ def svg_format(signature)
267
+ content = signature.sub(/\A\xEF\xBB\xBF/n, "").lstrip
268
+ return nil unless SVG_SIGNATURES.any? { |svg_signature| content.start_with?(svg_signature) }
269
+
270
+ :svg
271
+ end
272
+
273
+ def uploaded_file?(file)
274
+ return true if defined?(Rack::Test::UploadedFile) && file.is_a?(Rack::Test::UploadedFile)
275
+
276
+ file.is_a?(ActionDispatch::Http::UploadedFile)
277
+ end
278
+ end
@@ -12,7 +12,7 @@
12
12
  </label>
13
13
 
14
14
  <div data-controller="accordion" class="radio-accordion-section content-block__description editor-content">
15
- <div id="panel-title--<%= panel_id %>" aria-hidden="true">
15
+ <div id="panel-title--<%= panel_id %>" aria-hidden="true" inert>
16
16
  <%= yield %>
17
17
  </div>
18
18
 
@@ -21,10 +21,6 @@
21
21
  <%= show_more_text %>
22
22
  </span>
23
23
  <%= icon "arrow-down-s-line" %>
24
- <span>
25
- <%= show_less_text %>
26
- </span>
27
- <%= icon "arrow-up-s-line" %>
28
24
  </button>
29
25
  </div>
30
26
  </div>
@@ -1,3 +1 @@
1
- $("#messages").append("<%= j(render "messages", sender: conversation.messages.first.sender, messages: [conversation.messages.first]).html_safe %>");
2
- $("div[data-add-message]").replaceWith("<%= j(render "reply", form:, conversation:).html_safe %>");
3
- window.initFoundation($("#new_message_"));
1
+ window.location.href = "<%= conversation_path(conversation) %>";
@@ -9,11 +9,9 @@
9
9
  %>
10
10
 
11
11
  <%= decidim_form_for(Decidim::User.new, namespace: "login", as: :user, url: path, html: { class: nil }) do |f| %>
12
- <div>
13
- <div class="flex flex-row">
14
- <%= icon "user-line", class: "w-6 h-6 text-gray fill-current" %>
15
- <h3 id="dialog-title-loginModal" class="h3 ml-1"><%= t(".please_log_in") %></h3>
16
- </div>
12
+ <div data-dialog-container>
13
+ <%= icon "user-line" %>
14
+ <h3 id="dialog-title-loginModal" tabindex="-1" data-dialog-title><%= t(".please_log_in") %></h3>
17
15
 
18
16
  <div>
19
17
  <% if current_organization.sign_in_enabled? %>
@@ -44,16 +42,16 @@
44
42
  <%= render partial: "decidim/devise/shared/omniauth_buttons", locals: { is_horizontal: true } %>
45
43
  <% end %>
46
44
  <% end %>
47
- <% if current_organization.sign_in_enabled? %>
48
- <div data-dialog-actions>
49
- <div class="form__wrapper-block w-full mb-4">
50
- <button type="submit" class="button button__lg button__secondary">
51
- <%= t("devise.sessions.new.log_in") %>
52
- </button>
53
- </div>
54
- </div>
55
- <% end %>
56
45
  </div>
57
46
  </div>
47
+ <% if current_organization.sign_in_enabled? %>
48
+ <div data-dialog-actions>
49
+ <div class="form__wrapper-block w-full mb-4">
50
+ <button type="submit" class="button button__lg button__secondary">
51
+ <%= t("devise.sessions.new.log_in") %>
52
+ </button>
53
+ </div>
54
+ </div>
55
+ <% end %>
58
56
  <% end %>
59
57
  <% end %>
@@ -8,7 +8,7 @@
8
8
  <% if organization.logo.attached? %>
9
9
  <%= image_tag(
10
10
  organization.attached_uploader(:logo).variant_url(:medium, host: organization.host),
11
- style: "max-height: 50px",
11
+ style: "max-height: 50px; display: inline-block",
12
12
  alt: organization_name(organization)
13
13
  ) %>
14
14
  <% else %>
@@ -34,6 +34,16 @@ if Rails.env.production? || Rails.env.test?
34
34
  Rack::Attack.throttle("limit password recovery attempts per email", limit: 5, period: 60.seconds) do |request|
35
35
  request.params["user"]["email"] if request.path == "/users/password" && request.post?
36
36
  end
37
+
38
+ # Throttle SMS verification confirmation attempts per IP to 10 reqs/minute
39
+ Rack::Attack.throttle("limit sms verification attempts per ip", limit: 10, period: 60.seconds) do |request|
40
+ request.ip if request.path.match?(%r{^/[^/]+/sms/authorizations(?:\.[^/]+)?$}) && request.put?
41
+ end
42
+
43
+ # Throttle postal letter verification confirmation attempts per IP to 10 reqs/minute
44
+ Rack::Attack.throttle("limit postal letter verification attempts per ip", limit: 10, period: 60.seconds) do |request|
45
+ request.ip if request.path.match?(%r{^/[^/]+/postal_letter/authorizations(?:\.[^/]+)?$}) && request.put?
46
+ end
37
47
  end
38
48
  end
39
49
  end
@@ -1508,7 +1508,6 @@ bg:
1508
1508
  layouts:
1509
1509
  decidim:
1510
1510
  announcements:
1511
- view_less: Покажи по-малко
1512
1511
  view_more: Още информация
1513
1512
  data_consent:
1514
1513
  details:
@@ -253,6 +253,7 @@ ca-IT:
253
253
  create: "%{user_name} ha afegit el component %{resource_name} a l'espai %{space_name}"
254
254
  delete: "%{user_name} ha eliminat el component %{resource_name} de l'espai %{space_name}"
255
255
  export_component: "%{user_name} ha exportat %{resource_name}%{component_name} de %{space_name} com %{format_name}"
256
+ menu_hidden: "%{user_name} ha amagat el component %{resource_name} del menú de l'espai de participació: %{space_name}"
256
257
  publish: "%{user_name} ha publicat el component %{resource_name} a l'espai %{space_name}"
257
258
  restore: "%{user_name} ha restaurat el component %{resource_name} a l'espai %{space_name}"
258
259
  soft_delete: "%{user_name} ha mogut el component %{resource_name} a l'espai %{space_name} a la paperera"
@@ -857,6 +858,8 @@ ca-IT:
857
858
  errors:
858
859
  files:
859
860
  file_cannot_be_processed: No s'ha pogut processar el fitxer
861
+ file_contains_unsafe_content: El fitxer conté contingut no segur
862
+ file_is_not_a_valid_image: El fitxer no és una imatge vàlida
860
863
  file_resolution_too_large: La resolució de l'arxiu és massa gran
861
864
  not_inside_organization: L'arxiu no està vinculat a cap organització.
862
865
  internal_server_error:
@@ -2064,7 +2067,6 @@ ca-IT:
2064
2067
  layouts:
2065
2068
  decidim:
2066
2069
  announcements:
2067
- view_less: Mostra menys
2068
2070
  view_more: Més informació
2069
2071
  data_consent:
2070
2072
  details:
@@ -253,6 +253,7 @@ ca:
253
253
  create: "%{user_name} ha afegit el component %{resource_name} a l'espai %{space_name}"
254
254
  delete: "%{user_name} ha eliminat el component %{resource_name} de l'espai %{space_name}"
255
255
  export_component: "%{user_name} ha exportat %{resource_name}%{component_name} de %{space_name} com %{format_name}"
256
+ menu_hidden: "%{user_name} ha amagat el component %{resource_name} del menú de l'espai de participació: %{space_name}"
256
257
  publish: "%{user_name} ha publicat el component %{resource_name} a l'espai %{space_name}"
257
258
  restore: "%{user_name} ha restaurat el component %{resource_name} a l'espai %{space_name}"
258
259
  soft_delete: "%{user_name} ha mogut el component %{resource_name} a l'espai %{space_name} a la paperera"
@@ -857,6 +858,8 @@ ca:
857
858
  errors:
858
859
  files:
859
860
  file_cannot_be_processed: No s'ha pogut processar el fitxer
861
+ file_contains_unsafe_content: El fitxer conté contingut no segur
862
+ file_is_not_a_valid_image: El fitxer no és una imatge vàlida
860
863
  file_resolution_too_large: La resolució de l'arxiu és massa gran
861
864
  not_inside_organization: L'arxiu no està vinculat a cap organització.
862
865
  internal_server_error:
@@ -2064,7 +2067,6 @@ ca:
2064
2067
  layouts:
2065
2068
  decidim:
2066
2069
  announcements:
2067
- view_less: Mostra menys
2068
2070
  view_more: Més informació
2069
2071
  data_consent:
2070
2072
  details:
@@ -1201,6 +1201,8 @@ cs:
1201
1201
  dynamic:
1202
1202
  screen_reader_explanation: Následující prvek je mapa, která prezentuje položky na této stránce jako mapové body. Prvek lze použít s čtečkou obrazovky, ale může být těžko srozumitelný.
1203
1203
  skip_button: Přeskočit mapu
1204
+ zoom_in: Přiblížit
1205
+ zoom_out: Oddálit
1204
1206
  static:
1205
1207
  latlng_text: 'zeměpisná šířka: %{latitude}, zeměpisná délka: %{longitude}'
1206
1208
  map_service_brand: OpenStreetMap
@@ -1537,6 +1539,9 @@ cs:
1537
1539
  log_in: přihlásit se
1538
1540
  never_logged_in: nikdy se nepřihlásil
1539
1541
  subject: Upozornění na neaktivní účet
1542
+ participatory_space:
1543
+ labels:
1544
+ reference: Odkaz na prostor
1540
1545
  passwords:
1541
1546
  update:
1542
1547
  error: Došlo k problému s aktualizací hesla.
@@ -1822,6 +1827,7 @@ cs:
1822
1827
  nickname_help: Vaše přezdívka v %{organization}. Může obsahovat pouze písmena, číslice, '-' a '_'.
1823
1828
  submit_button: Uložit
1824
1829
  subtitle: Pokud přijmete pozvánku, nastavte svou přezdívku a heslo.
1830
+ subtitle_no_password: Pokud přijmete pozvánku, nastavte prosím svou přezdívku.
1825
1831
  invitation_removed: Vaše pozvánka byla odstraněna.
1826
1832
  invitation_token_invalid: Předložený token je neplatný!
1827
1833
  new:
@@ -1829,6 +1835,7 @@ cs:
1829
1835
  submit_button: Odeslat pozvánku
1830
1836
  no_invitations_remaining: Žádné zbývající pozvánky
1831
1837
  send_instructions: Pozvánka byla odeslána na %{email}.
1838
+ updated: Pozvánka byla úspěšně přijata. Nyní jste přihlášeni.
1832
1839
  updated_not_active: Pozvánka úspěšně přijata.
1833
1840
  mailer:
1834
1841
  confirmation_instructions:
@@ -2065,7 +2072,6 @@ cs:
2065
2072
  layouts:
2066
2073
  decidim:
2067
2074
  announcements:
2068
- view_less: Zobrazit méně
2069
2075
  view_more: Více informací
2070
2076
  data_consent:
2071
2077
  details: