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.
- checksums.yaml +4 -4
- data/app/cells/decidim/content_blocks/participatory_space_main_data/title.erb +1 -14
- data/app/cells/decidim/following_cell.rb +1 -1
- data/app/cells/decidim/nav_links/show.erb +3 -1
- data/app/cells/decidim/participatory_space_private_user/show.erb +1 -1
- data/app/cells/decidim/share_text_widget/modal.erb +1 -0
- data/app/cells/decidim/share_widget/modal.erb +1 -0
- data/app/cells/decidim/share_widget/qr_code_modal.erb +2 -4
- data/app/commands/decidim/create_omniauth_registration.rb +17 -2
- data/app/controllers/concerns/decidim/devise_authentication_methods.rb +4 -0
- data/app/controllers/concerns/decidim/devise_controllers.rb +2 -0
- data/app/controllers/concerns/decidim/disable_redirection_to_external_host.rb +2 -0
- data/app/controllers/concerns/decidim/filter_resource.rb +2 -0
- data/app/controllers/concerns/decidim/impersonate_users.rb +2 -2
- data/app/controllers/concerns/decidim/locale_switcher.rb +62 -11
- data/app/controllers/concerns/decidim/needs_permission.rb +22 -10
- data/app/controllers/concerns/decidim/onboarding_action_methods.rb +2 -0
- data/app/controllers/concerns/decidim/paginable.rb +7 -3
- data/app/controllers/concerns/decidim/payload_info.rb +2 -0
- data/app/controllers/concerns/decidim/resource_versions_concern.rb +14 -0
- data/app/controllers/decidim/application_controller.rb +0 -6
- data/app/controllers/decidim/authorization_modals_controller.rb +2 -0
- data/app/controllers/decidim/components/base_controller.rb +2 -0
- data/app/controllers/decidim/devise/passwords_controller.rb +8 -0
- data/app/controllers/decidim/favicon_controller.rb +9 -0
- data/app/controllers/decidim/free_resource_authorization_modals_controller.rb +2 -0
- data/app/controllers/decidim/gamification/badges_controller.rb +2 -0
- data/app/controllers/decidim/homepage_controller.rb +4 -1
- data/app/controllers/decidim/likes_controller.rb +1 -1
- data/app/controllers/decidim/links_controller.rb +2 -0
- data/app/controllers/decidim/manifests_controller.rb +2 -0
- data/app/controllers/decidim/messaging/conversations_controller.rb +10 -4
- data/app/controllers/decidim/newsletters_controller.rb +4 -0
- data/app/controllers/decidim/notifications_subscriptions_controller.rb +4 -0
- data/app/controllers/decidim/offline_controller.rb +5 -1
- data/app/controllers/decidim/open_data_controller.rb +5 -1
- data/app/controllers/decidim/pages_controller.rb +31 -3
- data/app/controllers/decidim/private_downloads_controller.rb +2 -0
- data/app/controllers/decidim/profiles_controller.rb +10 -0
- data/app/controllers/decidim/qr_controller.rb +3 -1
- data/app/controllers/decidim/resource_autocomplete_controller.rb +2 -0
- data/app/controllers/decidim/searches_controller.rb +2 -0
- data/app/controllers/decidim/short_links_controller.rb +4 -0
- data/app/controllers/decidim/static_map_controller.rb +2 -0
- data/app/controllers/decidim/timeouts_controller.rb +4 -0
- data/app/controllers/decidim/tos_controller.rb +2 -0
- data/app/controllers/decidim/upload_validations_controller.rb +4 -2
- data/app/controllers/decidim/user_activities_controller.rb +2 -0
- data/app/forms/decidim/upload_validation_form.rb +16 -10
- data/app/helpers/concerns/decidim/user_role_checker.rb +35 -10
- data/app/helpers/decidim/sanitize_helper.rb +11 -1
- data/app/jobs/decidim/application_job.rb +4 -0
- data/app/models/decidim/authorization.rb +24 -0
- data/app/models/decidim/oauth_application.rb +1 -0
- data/app/models/decidim/user.rb +1 -0
- data/app/models/decidim/user_base_entity.rb +1 -1
- data/app/packs/src/decidim/callout.js +10 -0
- data/app/packs/src/decidim/callout.test.js +32 -0
- data/app/packs/src/decidim/controllers/accordion/accordion.test.js +64 -0
- data/app/packs/src/decidim/controllers/accordion/controller.js +43 -0
- data/app/packs/src/decidim/datepicker/datepicker_functions.js +34 -23
- data/app/packs/src/decidim/datepicker/generate_datepicker.js +4 -4
- data/app/packs/src/decidim/datepicker/test/time.test.js +39 -24
- data/app/packs/src/decidim/utilities/dom.js +2 -2
- data/app/packs/stylesheets/decidim/_content_blocks.scss +4 -10
- data/app/packs/stylesheets/decidim/_conversations.scss +0 -8
- data/app/packs/stylesheets/decidim/_modal.scss +16 -8
- data/app/packs/stylesheets/decidim/_modal_share.scss +0 -22
- data/app/packs/stylesheets/decidim/_participatory_spaces.scss +11 -1
- data/app/packs/stylesheets/decidim/_rich_text.scss +12 -9
- data/app/packs/stylesheets/decidim/_typography.scss +5 -2
- data/app/packs/stylesheets/decidim/legacy/email.scss +0 -5
- data/app/permissions/decidim/permissions.rb +20 -0
- data/app/presenters/decidim/admin_log/component_presenter.rb +1 -1
- data/app/presenters/decidim/log/base_presenter.rb +10 -1
- data/app/presenters/decidim/log/user_presenter.rb +2 -2
- data/app/serializers/decidim/exporters/open_data_blocked_user_serializer.rb +11 -2
- data/app/serializers/decidim/exporters/open_data_moderation_serializer.rb +25 -1
- data/app/services/decidim/svg_content_validator.rb +554 -0
- data/app/uploaders/decidim/application_uploader.rb +2 -2
- data/app/validators/uploader_image_content_validator.rb +278 -0
- data/app/views/decidim/application/_radio_accordion.html.erb +1 -5
- data/app/views/decidim/messaging/conversations/create.js.erb +1 -3
- data/app/views/decidim/shared/_login_modal.html.erb +12 -14
- data/app/views/layouts/decidim/_mailer_logo.html.erb +1 -1
- data/config/initializers/rack_attack.rb +10 -0
- data/config/locales/bg.yml +0 -1
- data/config/locales/ca-IT.yml +3 -1
- data/config/locales/ca.yml +3 -1
- data/config/locales/cs.yml +7 -1
- data/config/locales/de.yml +61 -3
- data/config/locales/en.yml +3 -1
- data/config/locales/es-MX.yml +3 -1
- data/config/locales/es-PY.yml +3 -1
- data/config/locales/es.yml +7 -5
- data/config/locales/eu.yml +155 -156
- data/config/locales/fi-plain.yml +1 -1
- data/config/locales/fi.yml +1 -1
- data/config/locales/fr-CA.yml +3 -1
- data/config/locales/fr.yml +3 -1
- data/config/locales/gl.yml +1441 -33
- data/config/locales/hu.yml +0 -1
- data/config/locales/ja.yml +1 -1
- data/config/locales/pl.yml +0 -1
- data/config/locales/pt-BR.yml +0 -1
- data/config/locales/ro-RO.yml +0 -1
- data/config/locales/sk.yml +2 -1
- data/config/locales/sv.yml +47 -10
- data/config/locales/tr-TR.yml +0 -2
- data/db/migrate/20260909000000_add_failed_attempts_and_locked_at_to_decidim_authorizations.rb +8 -0
- data/lib/decidim/core/test/shared_examples/participatory_space_members_page_examples.rb +77 -0
- data/lib/decidim/core/test/shared_examples/participatory_space_members_shared_examples.rb +85 -6
- data/lib/decidim/core/test/shared_examples/versions_controller_examples.rb +23 -1
- data/lib/decidim/core/version.rb +1 -1
- data/lib/decidim/core.rb +9 -0
- data/lib/decidim/has_upload_validations.rb +1 -0
- data/lib/decidim/legacy_form_builder.rb +4 -0
- data/lib/decidim/query_extensions.rb +2 -0
- 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
|
-
|
|
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
|
-
|
|
14
|
-
|
|
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
|
data/config/locales/bg.yml
CHANGED
data/config/locales/ca-IT.yml
CHANGED
|
@@ -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:
|
data/config/locales/ca.yml
CHANGED
|
@@ -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:
|
data/config/locales/cs.yml
CHANGED
|
@@ -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:
|