From 208dc01d3bf272e9bf10e72a8b7bb5a10f310d6f Mon Sep 17 00:00:00 2001 From: Johann Date: Wed, 8 Oct 2025 15:32:10 +0200 Subject: [PATCH 1/5] Add tests for newsletter and email digest scopes These methods didn't have proper model tests. --- spec/models/user_spec.rb | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index c8d6777c3..851f12deb 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -402,6 +402,26 @@ describe User do end end + describe ".newsletter" do + it "returns users subscribed to the newsletter" do + create(:user, newsletter: true, username: "Subscriber1") + create(:user, newsletter: true, username: "Subscriber2") + create(:user, newsletter: false, username: "NonSubscriber") + + expect(User.newsletter.pluck(:username)).to eq ["Subscriber1", "Subscriber2"] + end + end + + describe ".email_digest" do + it "returns users subscribed to email digests" do + create(:user, email_digest: true, username: "Digester1") + create(:user, email_digest: true, username: "Digester2") + create(:user, email_digest: false, username: "NonDigester") + + expect(User.email_digest.pluck(:username)).to eq ["Digester1", "Digester2"] + end + end + describe ".by_username_email_or_document_number" do let!(:larry) do create(:user, email: "larry@consul.dev", username: "Larry Bird", document_number: "12345678Z") From e7f2210380f04c0c65f01d0ea0d2b18839f410fc Mon Sep 17 00:00:00 2001 From: Johann Date: Tue, 16 Sep 2025 21:25:35 +0200 Subject: [PATCH 2/5] Add setting to require consent for notifications MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ensure GDPR compliance by default (Article 25 GDPR – privacy by design and by default). Under GDPR, consent must be freely given, specific, informed and unambiguous [1]. We were subscribing users without explicity consent, which goes against the "No pre-ticked boxes" principle. For compatibility with existing installations, we're using a setting, disabled by default. Once we release version 2.4.0 we will enable it by default, which won't affect existing installations but only new ones. [1] https://gdprinfo.eu/best-gdpr-newsletter-consent-examples-a-complete-guide-to-compliant-email-marketing --- .../admin/settings/features_tab_component.rb | 1 + app/models/setting.rb | 1 + app/models/user.rb | 3 ++ config/locales/en/settings.yml | 3 ++ config/locales/es/settings.yml | 3 ++ ...ove_default_value_in_user_notifications.rb | 9 +++++ db/schema.rb | 8 ++--- spec/models/user_spec.rb | 36 +++++++++++++++---- 8 files changed, 54 insertions(+), 10 deletions(-) create mode 100644 db/migrate/20251009084919_remove_default_value_in_user_notifications.rb diff --git a/app/components/admin/settings/features_tab_component.rb b/app/components/admin/settings/features_tab_component.rb index 67d54d337..9b06e32a3 100644 --- a/app/components/admin/settings/features_tab_component.rb +++ b/app/components/admin/settings/features_tab_component.rb @@ -26,6 +26,7 @@ class Admin::Settings::FeaturesTabComponent < ApplicationComponent feature.sdg feature.machine_learning feature.remove_investments_supports + feature.gdpr.require_consent_for_notifications feature.dashboard.notification_emails ] end diff --git a/app/models/setting.rb b/app/models/setting.rb index d9b6f2fb1..7b997e563 100644 --- a/app/models/setting.rb +++ b/app/models/setting.rb @@ -91,6 +91,7 @@ class Setting < ApplicationRecord "feature.machine_learning": false, "feature.remove_investments_supports": true, "feature.cookies_consent": false, + "feature.gdpr.require_consent_for_notifications": false, "homepage.widgets.feeds.debates": true, "homepage.widgets.feeds.processes": true, "homepage.widgets.feeds.proposals": true, diff --git a/app/models/user.rb b/app/models/user.rb index 30cee72c1..a030f7422 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1,6 +1,9 @@ class User < ApplicationRecord include Verification attribute :registering_from_web, default: false + %i[newsletter email_digest email_on_direct_message].each do |field| + attribute field, :boolean, default: -> { !Setting["feature.gdpr.require_consent_for_notifications"] } + end devise :database_authenticatable, :registerable, :confirmable, :recoverable, :rememberable, :trackable, :validatable, :omniauthable, :password_expirable, :secure_validatable, diff --git a/config/locales/en/settings.yml b/config/locales/en/settings.yml index 50ace1eb0..fc4e95012 100644 --- a/config/locales/en/settings.yml +++ b/config/locales/en/settings.yml @@ -140,6 +140,9 @@ en: sdg_description: Enable Sustainable Development Goals sections in the administration menu and in the Global Settings. cookies_consent: Cookies consent banner cookies_consent_description: Enable the cookies consent banner to inform users about the cookies the application uses. + gdpr: + require_consent_for_notifications: Explicit consent for notifications + require_consent_for_notifications_description: Require explicit user consent in order to send them newsletters and similar information as required by the General Data Protection Regulation (GDPR). remote_census: general: endpoint: "Endpoint" diff --git a/config/locales/es/settings.yml b/config/locales/es/settings.yml index 02bb55f5c..77c1acf87 100644 --- a/config/locales/es/settings.yml +++ b/config/locales/es/settings.yml @@ -140,6 +140,9 @@ es: sdg_description: Habilitar secciones relacionadas con Objetivos de Desarrollo Sostenible en el menú de administración y en la sección de Configuración Global. cookies_consent: Banner de consentimiento de cookies cookies_consent_description: Activa el banner de consentimiento de cookies para informar a los usuarios sobre las cookies que utiliza la aplicación. + gdpr: + require_consent_for_notifications: Consentimiento explícito para notificaciones + require_consent_for_notifications_description: Requerir que los usuarios tengan que dar consentimiento explícito para enviarles boletines e información similar tal y como describe Reglamento General de Protección de Datos (RGPD). remote_census: general: endpoint: "Endpoint" diff --git a/db/migrate/20251009084919_remove_default_value_in_user_notifications.rb b/db/migrate/20251009084919_remove_default_value_in_user_notifications.rb new file mode 100644 index 000000000..0c0d61d74 --- /dev/null +++ b/db/migrate/20251009084919_remove_default_value_in_user_notifications.rb @@ -0,0 +1,9 @@ +class RemoveDefaultValueInUserNotifications < ActiveRecord::Migration[7.1] + def change + change_table :users do |t| + t.change_default :newsletter, from: true, to: nil + t.change_default :email_digest, from: true, to: nil + t.change_default :email_on_direct_message, from: true, to: nil + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 10cc0d023..c7eb4545c 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.1].define(version: 2025_09_09_145207) do +ActiveRecord::Schema[7.1].define(version: 2025_10_09_084919) do # These are extensions that must be enabled in order to support this database enable_extension "pg_trgm" enable_extension "plpgsql" @@ -1609,7 +1609,7 @@ ActiveRecord::Schema[7.1].define(version: 2025_09_09_145207) do t.string "erase_reason" t.datetime "erased_at", precision: nil t.boolean "public_activity", default: true - t.boolean "newsletter", default: true + t.boolean "newsletter" t.integer "notifications_count", default: 0 t.boolean "registering_with_oauth", default: false t.string "locale" @@ -1617,8 +1617,8 @@ ActiveRecord::Schema[7.1].define(version: 2025_09_09_145207) do t.integer "geozone_id" t.string "gender", limit: 10 t.datetime "date_of_birth", precision: nil - t.boolean "email_digest", default: true - t.boolean "email_on_direct_message", default: true + t.boolean "email_digest" + t.boolean "email_on_direct_message" t.boolean "official_position_badge", default: false t.datetime "password_changed_at", precision: nil, default: "2015-01-01 01:01:01", null: false t.boolean "created_from_signature", default: false diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 851f12deb..4a9a9139b 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -105,20 +105,44 @@ describe User do end describe "subscription_to_website_newsletter" do - it "is true by default" do - expect(subject.newsletter).to be true + it "is true by default when the consent for notifications setting is disabled" do + Setting["feature.gdpr.require_consent_for_notifications"] = false + + expect(build(:user).newsletter).to be true + end + + it "is false by default when the consent for notifications setting is enabled" do + Setting["feature.gdpr.require_consent_for_notifications"] = true + + expect(build(:user).newsletter).to be false end end describe "email_digest" do - it "is true by default" do - expect(subject.email_digest).to be true + it "is true by default when the consent for notifications setting is disabled" do + Setting["feature.gdpr.require_consent_for_notifications"] = false + + expect(build(:user).email_digest).to be true + end + + it "is false by default when the consent for notifications setting is enabled" do + Setting["feature.gdpr.require_consent_for_notifications"] = true + + expect(build(:user).email_digest).to be false end end describe "email_on_direct_message" do - it "is true by default" do - expect(subject.email_on_direct_message).to be true + it "is true by default when the consent for notifications setting is disabled" do + Setting["feature.gdpr.require_consent_for_notifications"] = false + + expect(build(:user).email_on_direct_message).to be true + end + + it "is false by default when the consent for notifications setting is enabled" do + Setting["feature.gdpr.require_consent_for_notifications"] = true + + expect(build(:user).email_on_direct_message).to be false end end From a1714fea589b3b19a657ef2f26e5ae3a5a835a37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javi=20Mart=C3=ADn?= Date: Wed, 8 Oct 2025 16:18:06 +0200 Subject: [PATCH 3/5] Use the "#" convention in user preferences methods tests This is a convention we follow most of the time, particularly in the last few years. --- spec/models/user_spec.rb | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 4a9a9139b..3d95d6fc3 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -92,19 +92,19 @@ describe User do end describe "preferences" do - describe "email_on_comment" do + describe "#email_on_comment" do it "is false by default" do expect(subject.email_on_comment).to be false end end - describe "email_on_comment_reply" do + describe "#email_on_comment_reply" do it "is false by default" do expect(subject.email_on_comment_reply).to be false end end - describe "subscription_to_website_newsletter" do + describe "#newsletter" do it "is true by default when the consent for notifications setting is disabled" do Setting["feature.gdpr.require_consent_for_notifications"] = false @@ -118,7 +118,7 @@ describe User do end end - describe "email_digest" do + describe "#email_digest" do it "is true by default when the consent for notifications setting is disabled" do Setting["feature.gdpr.require_consent_for_notifications"] = false @@ -132,7 +132,7 @@ describe User do end end - describe "email_on_direct_message" do + describe "#email_on_direct_message" do it "is true by default when the consent for notifications setting is disabled" do Setting["feature.gdpr.require_consent_for_notifications"] = false @@ -146,7 +146,7 @@ describe User do end end - describe "official_position_badge" do + describe "#official_position_badge" do it "is false by default" do expect(subject.official_position_badge).to be false end From 92a76dd46e6215d42eb24b9607358142a0a8f627 Mon Sep 17 00:00:00 2001 From: Johann Date: Wed, 8 Oct 2025 15:35:13 +0200 Subject: [PATCH 4/5] Disable recommendations by default when requiring consent The GDPR is open for interpretation, and it isn't clear whether showing users recommended proposals and debates while browsing the site is considered a notification that needs to be explicitly accepted. Since we aren't sure whether this is necessary, we're taking the safe approach and disabling recommendations by default. --- app/models/user.rb | 3 +- ...e_default_value_in_user_recommendations.rb | 8 ++++++ db/schema.rb | 6 ++-- spec/models/user_spec.rb | 28 +++++++++++++++++++ 4 files changed, 41 insertions(+), 4 deletions(-) create mode 100644 db/migrate/20251009085327_remove_default_value_in_user_recommendations.rb diff --git a/app/models/user.rb b/app/models/user.rb index a030f7422..6e5db433f 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1,7 +1,8 @@ class User < ApplicationRecord include Verification attribute :registering_from_web, default: false - %i[newsletter email_digest email_on_direct_message].each do |field| + %i[newsletter email_digest email_on_direct_message recommended_debates + recommended_proposals].each do |field| attribute field, :boolean, default: -> { !Setting["feature.gdpr.require_consent_for_notifications"] } end diff --git a/db/migrate/20251009085327_remove_default_value_in_user_recommendations.rb b/db/migrate/20251009085327_remove_default_value_in_user_recommendations.rb new file mode 100644 index 000000000..557c01a6b --- /dev/null +++ b/db/migrate/20251009085327_remove_default_value_in_user_recommendations.rb @@ -0,0 +1,8 @@ +class RemoveDefaultValueInUserRecommendations < ActiveRecord::Migration[7.1] + def change + change_table :users do |t| + t.change_default :recommended_debates, from: true, to: nil + t.change_default :recommended_proposals, from: true, to: nil + end + end +end diff --git a/db/schema.rb b/db/schema.rb index c7eb4545c..d74ea7a2e 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.1].define(version: 2025_10_09_084919) do +ActiveRecord::Schema[7.1].define(version: 2025_10_09_085327) do # These are extensions that must be enabled in order to support this database enable_extension "pg_trgm" enable_extension "plpgsql" @@ -1625,8 +1625,8 @@ ActiveRecord::Schema[7.1].define(version: 2025_10_09_084919) do t.integer "failed_email_digests_count", default: 0 t.text "former_users_data_log", default: "" t.boolean "public_interests", default: false - t.boolean "recommended_debates", default: true - t.boolean "recommended_proposals", default: true + t.boolean "recommended_debates" + t.boolean "recommended_proposals" t.string "subscriptions_token" t.integer "failed_attempts", default: 0, null: false t.datetime "locked_at", precision: nil diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 3d95d6fc3..843f475d1 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -146,6 +146,34 @@ describe User do end end + describe "#recommended_debates" do + it "is true by default when the consent for notifications setting is disabled" do + Setting["feature.gdpr.require_consent_for_notifications"] = false + + expect(build(:user).recommended_debates).to be true + end + + it "is false by default when the consent for notifications setting is enabled" do + Setting["feature.gdpr.require_consent_for_notifications"] = true + + expect(build(:user).recommended_debates).to be false + end + end + + describe "#recommended_proposals" do + it "is true by default when the consent for notifications setting is disabled" do + Setting["feature.gdpr.require_consent_for_notifications"] = false + + expect(build(:user).recommended_proposals).to be true + end + + it "is false by default when the consent for notifications setting is enabled" do + Setting["feature.gdpr.require_consent_for_notifications"] = true + + expect(build(:user).recommended_proposals).to be false + end + end + describe "#official_position_badge" do it "is false by default" do expect(subject.official_position_badge).to be false From 6d30e2d34e74893262d45322c09d4b49afc2f619 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javi=20Mart=C3=ADn?= Date: Wed, 8 Oct 2025 15:37:33 +0200 Subject: [PATCH 5/5] Don't display public activity by default when requiring consent Just as we mentioned in the previous commit, there are places where we aren't sure whether explicit consent is strictly required. So, when the "require consent" setting is enabled, we're taking the safe approach. This means that, in this case, we're only displaying a user's activity if they've given explicit consent. --- app/models/user.rb | 2 +- ...remove_default_value_in_user_public_activity.rb | 7 +++++++ db/schema.rb | 4 ++-- spec/factories/users.rb | 1 - spec/models/user_spec.rb | 14 ++++++++++++++ 5 files changed, 24 insertions(+), 4 deletions(-) create mode 100644 db/migrate/20251009085528_remove_default_value_in_user_public_activity.rb diff --git a/app/models/user.rb b/app/models/user.rb index 6e5db433f..7af90cdbe 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1,7 +1,7 @@ class User < ApplicationRecord include Verification attribute :registering_from_web, default: false - %i[newsletter email_digest email_on_direct_message recommended_debates + %i[newsletter email_digest email_on_direct_message public_activity recommended_debates recommended_proposals].each do |field| attribute field, :boolean, default: -> { !Setting["feature.gdpr.require_consent_for_notifications"] } end diff --git a/db/migrate/20251009085528_remove_default_value_in_user_public_activity.rb b/db/migrate/20251009085528_remove_default_value_in_user_public_activity.rb new file mode 100644 index 000000000..315be7a56 --- /dev/null +++ b/db/migrate/20251009085528_remove_default_value_in_user_public_activity.rb @@ -0,0 +1,7 @@ +class RemoveDefaultValueInUserPublicActivity < ActiveRecord::Migration[7.1] + def change + change_table :users do |t| + t.change_default :public_activity, from: true, to: nil + end + end +end diff --git a/db/schema.rb b/db/schema.rb index d74ea7a2e..f003810bc 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.1].define(version: 2025_10_09_085327) do +ActiveRecord::Schema[7.1].define(version: 2025_10_09_085528) do # These are extensions that must be enabled in order to support this database enable_extension "pg_trgm" enable_extension "plpgsql" @@ -1608,7 +1608,7 @@ ActiveRecord::Schema[7.1].define(version: 2025_10_09_085327) do t.datetime "level_two_verified_at", precision: nil t.string "erase_reason" t.datetime "erased_at", precision: nil - t.boolean "public_activity", default: true + t.boolean "public_activity" t.boolean "newsletter" t.integer "notifications_count", default: 0 t.boolean "registering_with_oauth", default: false diff --git a/spec/factories/users.rb b/spec/factories/users.rb index d9b8789c5..cad4c8880 100644 --- a/spec/factories/users.rb +++ b/spec/factories/users.rb @@ -7,7 +7,6 @@ FactoryBot.define do terms_of_service { "1" } confirmed_at { Time.current } date_of_birth { 20.years.ago } - public_activity { true } trait :incomplete_verification do after :create do |user| diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 843f475d1..a986f556e 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -146,6 +146,20 @@ describe User do end end + describe "#public_activity" do + it "is true by default when the consent for notifications setting is disabled" do + Setting["feature.gdpr.require_consent_for_notifications"] = false + + expect(build(:user).public_activity).to be true + end + + it "is false by default when the consent for notifications setting is enabled" do + Setting["feature.gdpr.require_consent_for_notifications"] = true + + expect(build(:user).public_activity).to be false + end + end + describe "#recommended_debates" do it "is true by default when the consent for notifications setting is disabled" do Setting["feature.gdpr.require_consent_for_notifications"] = false