From 21dd44351f9d26d57e153555f7e0e898636d430f Mon Sep 17 00:00:00 2001 From: f Date: Thu, 20 Aug 2020 20:38:31 -0300 Subject: [PATCH] los datos privados se cifran! --- app/models/metadata_encrypted_text.rb | 33 ++++----------------- app/models/metadata_geo.rb | 15 ++++++++++ app/models/metadata_number.rb | 7 +++++ app/models/metadata_order.rb | 5 ++++ app/models/metadata_related_posts.rb | 5 ++++ app/models/metadata_slug.rb | 5 +++- app/models/metadata_template.rb | 42 ++++++++++++++++++++++++++- app/models/site.rb | 6 ++++ 8 files changed, 88 insertions(+), 30 deletions(-) diff --git a/app/models/metadata_encrypted_text.rb b/app/models/metadata_encrypted_text.rb index 4a20886c..b12f22e0 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 1fb3febc..7f9edbff 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 9422ed16..5005b612 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 e173e467..27c5b8ca 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 a0c5fba0..5b73857c 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 f11fb29f..a0844c62 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 636ccfd2..81ee17f8 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 b371f6c4..36bf0cd0 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!