diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e810806..971805b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -13,7 +13,7 @@ env: jobs: test: name: Test - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 services: postgres: image: postgres:11 diff --git a/.gitignore b/.gitignore index 6282880..87bc84f 100644 --- a/.gitignore +++ b/.gitignore @@ -42,6 +42,11 @@ /public/packs-test /public/sw.js /public/sw.js.map +public/sw.js.LICENSE.txt +public/sw.js.br +public/sw.js.gz +public/sw.js.map.br +public/sw.js.map.gz # Ignore node modules /node_modules diff --git a/Gemfile b/Gemfile index d55f9ff..9616dc2 100644 --- a/Gemfile +++ b/Gemfile @@ -14,6 +14,8 @@ gem "decidim", DECIDIM_VERSION # gem "decidim-templates", DECIDIM_VERSION gem "decidim-decidim_awesome" +gem "decidim-direct_verifications", git: "https://github.com/Platoniq/decidim-verifications-direct_verifications" +gem "decidim-kids", git: "https://github.com/AjuntamentdeBarcelona/decidim-module-kids" gem "decidim-term_customizer", git: "https://github.com/mainio/decidim-module-term_customizer", branch: "main" gem "bootsnap", "~> 1.3" diff --git a/Gemfile.lock b/Gemfile.lock index a301068..648eb35 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,3 +1,22 @@ +GIT + remote: https://github.com/AjuntamentdeBarcelona/decidim-module-kids + revision: df16cf4de13c306348761d850d19815c8221874e + specs: + decidim-kids (0.2.0) + decidim-core (>= 0.28, < 0.29) + decidim-system (>= 0.28, < 0.29) + decidim-verifications (>= 0.28, < 0.29) + deface (>= 1.5) + +GIT + remote: https://github.com/Platoniq/decidim-verifications-direct_verifications + revision: f55f45c58a34b468f5eb753cc988233ec11d561c + specs: + decidim-direct_verifications (1.4.0) + decidim-admin (>= 0.28.0, < 0.29) + decidim-core (>= 0.28.0, < 0.29) + deface (~> 1.5) + GIT remote: https://github.com/mainio/decidim-module-term_customizer revision: 9133eea57ebfc4164b640efd1ac6b9ca1628c793 @@ -887,6 +906,8 @@ DEPENDENCIES decidim (= 0.28.2) decidim-decidim_awesome decidim-dev (= 0.28.2) + decidim-direct_verifications! + decidim-kids! decidim-term_customizer! figaro (~> 1.2) letter_opener_web (~> 2.0) diff --git a/README.md b/README.md index 7db0aaf..9afb84c 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,25 @@ Free Open-Source participatory democracy, citizen participation and open governm This is the open-source repository for decidim_inspire, based on [Decidim](https://github.com/decidim/decidim). +## Custom census authorization handler + +This authorization handler allows users to be directly verified with their birthdates by checking the records in a table. + +You need to create records for the model `Decidim::CustomCensusRecord`. For example: + +```ruby +[ + { email: "john.doe@example.org", date_of_birth: "1956-03-14" }, + { email: "jane.smith@example.org", date_of_birth: "1998-12-06" } +].each do |record| + Decidim::CustomCensusRecord.create(email: record[:email], metadata: { date_of_birth: record[:date_of_birth] }) +end +``` + +The verification will succeed if the user is in the census and introduces the same birthdate as the one in the database. + +This authorization handler will allow us to work with the [Decidim Kids](https://github.com/AjuntamentdeBarcelona/decidim-module-kids) module. + ## Setting up the application You will need to do some steps before having the app working properly once you have deployed it: diff --git a/app/models/decidim/custom_census_record.rb b/app/models/decidim/custom_census_record.rb new file mode 100644 index 0000000..ab37093 --- /dev/null +++ b/app/models/decidim/custom_census_record.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module Decidim + class CustomCensusRecord < ApplicationRecord + include Decidim::RecordEncryptor + + validates :email, uniqueness: true + + encrypt_attribute :metadata, type: :hash + + def date_of_birth + metadata["date_of_birth"] + end + end +end diff --git a/app/services/custom_census_authorization_handler.rb b/app/services/custom_census_authorization_handler.rb new file mode 100644 index 0000000..08119e6 --- /dev/null +++ b/app/services/custom_census_authorization_handler.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +# Checks the authorization against the census for Barcelona. +require "digest/md5" + +# This class performs a check against the census file in order to verify the citizen's residence. +class CustomCensusAuthorizationHandler < Decidim::AuthorizationHandler + attribute :date_of_birth, Date + + validates :date_of_birth, presence: true + + validate :present_in_census + + def metadata + super.merge(date_of_birth: parsed_date_of_birth) + end + + def unique_id + Digest::MD5.hexdigest("#{user.email}-#{Rails.application.secrets.secret_key_base}") + end + + private + + def parsed_date_of_birth + @parsed_date_of_birth ||= date_of_birth&.strftime("%Y-%m-%d") + end + + def present_in_census + record = Decidim::CustomCensusRecord.find_by(email: user.email) + return errors.add(:base, I18n.t("custom_census_authorization_handler.errors.not_found")) unless record + + errors.add(:date_of_birth, I18n.t("custom_census_authorization_handler.errors.invalid_date_of_birth")) unless record.date_of_birth == parsed_date_of_birth + end +end diff --git a/app/views/custom_census_authorization/_form.html.erb b/app/views/custom_census_authorization/_form.html.erb new file mode 100644 index 0000000..5aaef1b --- /dev/null +++ b/app/views/custom_census_authorization/_form.html.erb @@ -0,0 +1,7 @@ +
+
+ <%= form.date_select :date_of_birth, start_year: 1900, end_year: 1.year.ago.year, default: 20.years.ago, prompt: { day: t(".date_select.day"), month: t(".date_select.month"), year: t(".date_select.year") } %> +
+ + <%= form.hidden_field :handler_name %> +
diff --git a/config/i18n-tasks.yml b/config/i18n-tasks.yml index 621c5a1..e106c5b 100644 --- a/config/i18n-tasks.yml +++ b/config/i18n-tasks.yml @@ -11,3 +11,5 @@ ignore_missing: - layouts.decidim.footer.cc_by_license - layouts.decidim.footer.decidim_logo - layouts.decidim.footer.made_with_open_source + - custom_census_authorization.form.* + - custom_census_authorization_handler.errors.* diff --git a/config/initializers/decidim.rb b/config/initializers/decidim.rb index 253e436..1b35fa5 100644 --- a/config/initializers/decidim.rb +++ b/config/initializers/decidim.rb @@ -494,3 +494,9 @@ # Inform Decidim about the assets folder Decidim.register_assets_path File.expand_path("app/packs", Rails.application.root) + +Decidim::Verifications.register_workflow(:custom_census_authorization_handler) do |auth| + auth.form = "CustomCensusAuthorizationHandler" + auth.renewable = true + auth.time_between_renewals = 1.day +end diff --git a/config/locales/en.yml b/config/locales/en.yml index 63f1c3e..12b9d2a 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1 +1,27 @@ en: + activemodel: + attributes: + custom_census_authorization_handler: + date_of_birth: Date of birth + custom_census_authorization: + form: + date_select: + day: Day + month: Month + year: Year + custom_census_authorization_handler: + errors: + not_found: The user is not present in the census + invalid_date_of_birth: The date of birth is not correct + decidim: + authorization_handlers: + custom_census_authorization_handler: + explanation: Verify against the custom census authorization handler + fields: + date_of_birth: Date of birth + name: Custom census + verifications: + authorizations: + first_login: + actions: + custom_census_authorization_handler: Verify against the custom census authorization handler diff --git a/db/migrate/20241011083922_create_organization_minors_configs.decidim_kids.rb b/db/migrate/20241011083922_create_organization_minors_configs.decidim_kids.rb new file mode 100644 index 0000000..2cbc978 --- /dev/null +++ b/db/migrate/20241011083922_create_organization_minors_configs.decidim_kids.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true +# This migration comes from decidim_kids (originally 20221017110422) + +class CreateOrganizationMinorsConfigs < ActiveRecord::Migration[5.2] + def change + create_table :decidim_kids_organization_configs do |t| + t.integer :decidim_organization_id, null: false, index: { name: "index_decidim_kids_organization" } + t.boolean :enable_minors_participation, null: false, default: false + t.integer :minimum_minor_age, null: false, default: 10 + t.integer :maximum_minor_age, null: false, default: 13 + t.string :minors_authorization + t.string :tutors_authorization + t.integer :maximum_minor_accounts, null: false, default: 3 + t.timestamps + end + end +end diff --git a/db/migrate/20241011083923_create_decidim_kids_minor_accounts.decidim_kids.rb b/db/migrate/20241011083923_create_decidim_kids_minor_accounts.decidim_kids.rb new file mode 100644 index 0000000..d16f4bc --- /dev/null +++ b/db/migrate/20241011083923_create_decidim_kids_minor_accounts.decidim_kids.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true +# This migration comes from decidim_kids (originally 20221024124523) + +class CreateDecidimKidsMinorAccounts < ActiveRecord::Migration[6.1] + def change + create_table :decidim_kids_minor_accounts do |t| + t.references :decidim_tutor, null: false, index: true, foreign_key: { to_table: "decidim_users" } + t.references :decidim_minor, null: false, index: true, foreign_key: { to_table: "decidim_users" } + t.timestamps + end + + add_index :decidim_kids_minor_accounts, [:decidim_tutor_id, :decidim_minor_id], unique: true, name: "decidim_kids_minor_accounts_unique_tutor_and_minor_ids" + end +end diff --git a/db/migrate/20241011083924_create_decidim_kids_minor_data.decidim_kids.rb b/db/migrate/20241011083924_create_decidim_kids_minor_data.decidim_kids.rb new file mode 100644 index 0000000..25d3962 --- /dev/null +++ b/db/migrate/20241011083924_create_decidim_kids_minor_data.decidim_kids.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true +# This migration comes from decidim_kids (originally 20221027211859) + +class CreateDecidimKidsMinorData < ActiveRecord::Migration[6.1] + def change + create_table :decidim_kids_minor_data do |t| + t.references :decidim_user, null: false, index: true + t.string :name # encrypted + t.string :birthday # encrypted + t.string :email # encrypted + t.jsonb :extra_data, null: false, default: {} + t.timestamps + end + end +end diff --git a/db/migrate/20241011083925_create_impersonation_minor_logs.decidim_kids.rb b/db/migrate/20241011083925_create_impersonation_minor_logs.decidim_kids.rb new file mode 100644 index 0000000..c4e1130 --- /dev/null +++ b/db/migrate/20241011083925_create_impersonation_minor_logs.decidim_kids.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true +# This migration comes from decidim_kids (originally 20221127103636) + +class CreateImpersonationMinorLogs < ActiveRecord::Migration[6.1] + def change + create_table :decidim_kids_impersonation_minor_logs do |t| + t.references :decidim_tutor, index: true + t.references :decidim_minor, index: true + t.datetime :started_at + t.datetime :ended_at + t.datetime :expired_at + + t.timestamps + end + end +end diff --git a/db/migrate/20241011083926_create_decidim_kids_participatory_spaces_minors_configs.decidim_kids.rb b/db/migrate/20241011083926_create_decidim_kids_participatory_spaces_minors_configs.decidim_kids.rb new file mode 100644 index 0000000..228fbb8 --- /dev/null +++ b/db/migrate/20241011083926_create_decidim_kids_participatory_spaces_minors_configs.decidim_kids.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true +# This migration comes from decidim_kids (originally 20221220132306) + +class CreateDecidimKidsParticipatorySpacesMinorsConfigs < ActiveRecord::Migration[6.1] + def change + create_table :decidim_kids_participatory_spaces_minors_configs do |t| + t.string :access_type, null: false, default: "all" + t.string :authorization + t.integer :max_age, null: false, default: 16 + t.references :participatory_space, polymorphic: true, index: { name: "index_minor_config_on_space_type_and_id" } + t.timestamps + end + end +end diff --git a/db/migrate/20250115084854_create_decidim_custom_census_records.rb b/db/migrate/20250115084854_create_decidim_custom_census_records.rb new file mode 100644 index 0000000..e78a952 --- /dev/null +++ b/db/migrate/20250115084854_create_decidim_custom_census_records.rb @@ -0,0 +1,13 @@ +class CreateDecidimCustomCensusRecords < ActiveRecord::Migration[6.1] + def change + create_table :decidim_custom_census_records do |t| + t.string :email, null: false + t.jsonb :metadata + + Decidim::Authorization + t.timestamps + end + + add_index :decidim_custom_census_records, :email, unique: true + end +end diff --git a/db/schema.rb b/db/schema.rb index 505bb4c..1705c75 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.define(version: 2024_09_06_160616) do +ActiveRecord::Schema.define(version: 2025_01_15_084854) do # These are extensions that must be enabled in order to support this database enable_extension "ltree" @@ -550,6 +550,14 @@ t.index ["section_id"], name: "index_decidim_contextual_help_sections_on_section_id" end + create_table "decidim_custom_census_records", force: :cascade do |t| + t.string "email", null: false + t.jsonb "metadata" + t.datetime "created_at", precision: 6, null: false + t.datetime "updated_at", precision: 6, null: false + t.index ["email"], name: "index_decidim_custom_census_records_on_email", unique: true + end + create_table "decidim_debates_debates", id: :serial, force: :cascade do |t| t.jsonb "title" t.jsonb "description" @@ -742,6 +750,63 @@ t.index ["decidim_user_id"], name: "index_decidim_impersonation_logs_on_decidim_user_id" end + create_table "decidim_kids_impersonation_minor_logs", force: :cascade do |t| + t.bigint "decidim_tutor_id" + t.bigint "decidim_minor_id" + t.datetime "started_at" + t.datetime "ended_at" + t.datetime "expired_at" + t.datetime "created_at", precision: 6, null: false + t.datetime "updated_at", precision: 6, null: false + t.index ["decidim_minor_id"], name: "index_decidim_kids_impersonation_minor_logs_on_decidim_minor_id" + t.index ["decidim_tutor_id"], name: "index_decidim_kids_impersonation_minor_logs_on_decidim_tutor_id" + end + + create_table "decidim_kids_minor_accounts", force: :cascade do |t| + t.bigint "decidim_tutor_id", null: false + t.bigint "decidim_minor_id", null: false + t.datetime "created_at", precision: 6, null: false + t.datetime "updated_at", precision: 6, null: false + t.index ["decidim_minor_id"], name: "index_decidim_kids_minor_accounts_on_decidim_minor_id" + t.index ["decidim_tutor_id", "decidim_minor_id"], name: "decidim_kids_minor_accounts_unique_tutor_and_minor_ids", unique: true + t.index ["decidim_tutor_id"], name: "index_decidim_kids_minor_accounts_on_decidim_tutor_id" + end + + create_table "decidim_kids_minor_data", force: :cascade do |t| + t.bigint "decidim_user_id", null: false + t.string "name" + t.string "birthday" + t.string "email" + t.jsonb "extra_data", default: {}, null: false + t.datetime "created_at", precision: 6, null: false + t.datetime "updated_at", precision: 6, null: false + t.index ["decidim_user_id"], name: "index_decidim_kids_minor_data_on_decidim_user_id" + end + + create_table "decidim_kids_organization_configs", force: :cascade do |t| + t.integer "decidim_organization_id", null: false + t.boolean "enable_minors_participation", default: false, null: false + t.integer "minimum_minor_age", default: 10, null: false + t.integer "maximum_minor_age", default: 13, null: false + t.string "minors_authorization" + t.string "tutors_authorization" + t.integer "maximum_minor_accounts", default: 3, null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["decidim_organization_id"], name: "index_decidim_kids_organization" + end + + create_table "decidim_kids_participatory_spaces_minors_configs", force: :cascade do |t| + t.string "access_type", default: "all", null: false + t.string "authorization" + t.integer "max_age", default: 16, null: false + t.string "participatory_space_type" + t.bigint "participatory_space_id" + t.datetime "created_at", precision: 6, null: false + t.datetime "updated_at", precision: 6, null: false + t.index ["participatory_space_type", "participatory_space_id"], name: "index_minor_config_on_space_type_and_id" + end + create_table "decidim_meetings_agenda_items", force: :cascade do |t| t.bigint "decidim_agenda_id" t.integer "position" @@ -1777,6 +1842,8 @@ add_foreign_key "decidim_editor_images", "decidim_organizations" add_foreign_key "decidim_editor_images", "decidim_users", column: "decidim_author_id" add_foreign_key "decidim_identities", "decidim_organizations" + add_foreign_key "decidim_kids_minor_accounts", "decidim_users", column: "decidim_minor_id" + add_foreign_key "decidim_kids_minor_accounts", "decidim_users", column: "decidim_tutor_id" add_foreign_key "decidim_newsletters", "decidim_users", column: "author_id" add_foreign_key "decidim_participatory_process_steps", "decidim_participatory_processes" add_foreign_key "decidim_participatory_process_types", "decidim_organizations"