diff --git a/app/models/metadata_encrypted_text.rb b/app/models/metadata_encrypted_text.rb index 4a20886..b12f22e 100644 --- a/app/models/metadata_encrypted_text.rb +++ b/app/models/metadata_encrypted_text.rb @@ -1,34 +1,11 @@ # frozen_string_literal: true -# Íbamos a usar OpenSSL pero esto es más simple de implementar y no -# tenemos que usar cifrado compatible con JavaScript. +# Como todos los campos pueden ser cifrados, forzamos el cifrado +# configurando este texto como privado. +# +# TODO: Deprecar class MetadataEncryptedText < MetadataText - # Decifra el valor si está guardado en el sitio - def value - self[:value] ||= if (v = document.data.dig(name.to_s)) - box.decrypt_str v - else - default_value - end - rescue Lockbox::DecryptionError => e - ExceptionNotifier.notify_exception(e) - - self[:value] ||= I18n.t('lockbox.help.decryption_error') - end - - # Cifra el valor antes de guardarlo - def save - self[:value] = box.encrypt sanitize(value) - + def private? true end - - private - - # Genera una lockbox a partir de la llave privada del sitio - # - # @return [Lockbox] - def box - @box ||= Lockbox.new key: site.private_key, padding: true, encode: true - end end diff --git a/app/models/metadata_geo.rb b/app/models/metadata_geo.rb index 1fb3feb..7f9edbf 100644 --- a/app/models/metadata_geo.rb +++ b/app/models/metadata_geo.rb @@ -14,7 +14,22 @@ class MetadataGeo < MetadataTemplate return true if empty? self[:value] = value.transform_values(&:to_f) + self[:value] = encrypt(value) if private? true end + + private + + def encrypt(value) + value.transform_values do |v| + super v + end + end + + def decrypt(value) + value.transform_values do |v| + super v + end + end end diff --git a/app/models/metadata_number.rb b/app/models/metadata_number.rb index 9422ed1..5005b61 100644 --- a/app/models/metadata_number.rb +++ b/app/models/metadata_number.rb @@ -9,7 +9,14 @@ class MetadataNumber < MetadataTemplate def save self[:value] = value.to_i + self[:value] = encrypt(value) if private? true end + + private + + def decrypt(value) + super(value).to_i + end end diff --git a/app/models/metadata_order.rb b/app/models/metadata_order.rb index e173e46..27c5b8c 100644 --- a/app/models/metadata_order.rb +++ b/app/models/metadata_order.rb @@ -13,4 +13,9 @@ class MetadataOrder < MetadataTemplate true end + + # El orden nunca puede ser privado + def private? + false + end end diff --git a/app/models/metadata_related_posts.rb b/app/models/metadata_related_posts.rb index a0c5fba..5b73857 100644 --- a/app/models/metadata_related_posts.rb +++ b/app/models/metadata_related_posts.rb @@ -10,6 +10,11 @@ class MetadataRelatedPosts < MetadataArray end.inject(:merge) end + # Las relaciones nunca son privadas + def private? + false + end + private # Obtiene todos los posts y opcionalmente los filtra diff --git a/app/models/metadata_slug.rb b/app/models/metadata_slug.rb index f11fb29..a0844c6 100644 --- a/app/models/metadata_slug.rb +++ b/app/models/metadata_slug.rb @@ -38,7 +38,10 @@ class MetadataSlug < MetadataTemplate private + # Devuelve el título a menos que sea privado y no esté vacío def title - post.title.try(:value) unless post.title.try(:value).blank? + unless post.title.private? && !post.title.try(:value).try(:blank?) + post.title.try(:value).try(:to_s) + end end end diff --git a/app/models/metadata_template.rb b/app/models/metadata_template.rb index 636ccfd..81ee17f 100644 --- a/app/models/metadata_template.rb +++ b/app/models/metadata_template.rb @@ -24,7 +24,11 @@ MetadataTemplate = Struct.new(:site, :document, :name, :label, :type, # Valor actual o por defecto. Al memoizarlo podemos modificarlo # usando otros métodos que el de asignación. def value - self[:value] ||= document.data.fetch(name.to_s, default_value) + self[:value] ||= if private? + decrypt document.data.fetch(name.to_s, default_value) + else + document.data.fetch(name.to_s, default_value) + end end # Detecta si el valor está vacío @@ -74,6 +78,8 @@ MetadataTemplate = Struct.new(:site, :document, :name, :label, :type, # guardado def save self[:value] = sanitize value + self[:value] = encrypt(value) if private? + true end @@ -85,6 +91,11 @@ MetadataTemplate = Struct.new(:site, :document, :name, :label, :type, raise NotImplementedError end + # Determina si el campo es privado y debería ser cifrado + def private? + !!layout.metadata.dig(name, 'private') + end + private # Si es obligatorio no puede estar vacío @@ -104,5 +115,34 @@ MetadataTemplate = Struct.new(:site, :document, :name, :label, :type, attributes: allowed_attributes + %w[data-trix-attachment], scrubber: scrubber).strip.html_safe end + + # Decifra el valor + # + # XXX: Otros tipos de valores necesitan implementar su propio método + # de decifrado (Array). + def decrypt(value) + return value if value.blank? + + box.decrypt_str value.to_s + rescue Lockbox::DecryptionError => e + ExceptionNotifier.notify_exception(e) + + I18n.t('lockbox.help.decryption_error') + end + + # Cifra el valor. + # + # XXX: Otros tipos de valores necesitan implementar su propio método + # de cifrado (Array). + def encrypt(value) + box.encrypt value.to_s + end + + # Genera una lockbox a partir de la llave privada del sitio + # + # @return [Lockbox] + def box + @box ||= Lockbox.new key: site.private_key, padding: true, encode: true + end end # rubocop:enable Metrics/BlockLength diff --git a/app/models/site.rb b/app/models/site.rb index b371f6c..36bf0cd 100644 --- a/app/models/site.rb +++ b/app/models/site.rb @@ -57,6 +57,7 @@ class Site < ApplicationRecord after_create :load_jekyll, :static_file_migration! # Cambiar el nombre del directorio before_update :update_name! + before_save :add_private_key_if_missing! # Guardar la configuración si hubo cambios after_save :sync_attributes_with_config! @@ -391,6 +392,11 @@ class Site < ApplicationRecord private + # Asegurarse que el sitio tenga una llave privada + def add_private_key_if_missing! + self.private_key ||= Lockbox.generate_key + end + # Clona el esqueleto de Sutty para crear el sitio nuevo, no pasa nada # si el sitio ya existe def clone_skel!