diff --git a/.github/workflows/ci_cleaner.yml b/.github/workflows/ci_cleaner.yml index 3b0c64d..7205f3f 100644 --- a/.github/workflows/ci_cleaner.yml +++ b/.github/workflows/ci_cleaner.yml @@ -19,14 +19,7 @@ jobs: if: "github.ref != 'refs/heads/develop'" env: GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" - - uses: actions/checkout@v2.0.0 - with: - fetch-depth: 1 - - uses: ruby/setup-ruby@v1 - with: - bundler-cache: true - - name: Run Rubocop - run: bundle exec rubocop -P + - uses: OpenSourcePolitics/lint-action@master tests: name: Tests runs-on: ubuntu-latest @@ -51,86 +44,42 @@ jobs: if: "github.ref != 'refs/heads/develop'" env: GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" - - uses: actions/checkout@v2.0.0 - with: - fetch-depth: 1 - - uses: ruby/setup-ruby@v1 - with: - bundler-cache: true - - uses: actions/setup-node@v1 + - uses: OpenSourcePolitics/rspec-action@master with: - node-version: ${{ env.NODE_VERSION }} - - name: Get npm cache directory path - id: npm-cache-dir-path - run: echo "::set-output name=dir::$(npm get cache)-cleaner" - - uses: actions/cache@v2 - id: npm-cache - with: - path: ${{ steps.npm-cache-dir-path.outputs.dir }} - key: npm-${{ hashFiles('**/package-lock.json') }} - restore-keys: | - npm- - - run: bundle exec rake test_app - name: Create test app - - run: mkdir -p ./spec/decidim_dummy_app/tmp/screenshots - name: Create the screenshots folder - - uses: nanasess/setup-chromedriver@v1.0.1 - - name: Run precompile if needed - run: | - if [[ -d "app/views" ]] || [[ -d "spec/mailers" ]] || [[ -d "spec/system" ]]; then - cd "spec/decidim_dummy_app" - bundle exec rails assets:precompile - else - echo "No need to precompile assets since system folder is empty" - fi - - run: bundle exec rspec - name: RSpec - - uses: codecov/codecov-action@v1 - - uses: actions/upload-artifact@v2 - if: always() - with: - name: screenshots - path: ./spec/decidim_dummy_app/tmp/screenshots - if-no-files-found: ignore - release: - if: "github.ref == 'refs/heads/master' || github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/heads/release' )" - needs: [tests, lint] + command: "bundle exec rspec --exclude-pattern 'spec/system/**/*_spec.rb'" + system_tests: + name: System tests runs-on: ubuntu-latest + timeout-minutes: 30 + services: + postgres: + image: postgres:11 + ports: ["5432:5432"] + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + env: + POSTGRES_PASSWORD: postgres + env: + DATABASE_USERNAME: postgres + DATABASE_PASSWORD: postgres + DATABASE_HOST: localhost steps: - - uses: actions/checkout@v2.0.0 - with: - fetch-depth: 1 - - uses: ruby/setup-ruby@v1 + - uses: rokroskar/workflow-run-cleanup-action@v0.3.0 + if: "github.ref != 'refs/heads/develop'" + env: + GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" + - uses: OpenSourcePolitics/rspec-action@master with: - bundler-cache: true - - name: Setup git and gh - run: | - git config user.name "${GITHUB_ACTOR}" - git config user.email "${GITHUB_ACTOR}@users.noreply.github.com" - echo ${{ secrets.GITHUB_TOKEN }} | gh auth login --with-token - - run: gem install parse_gemspec-cli - name: Intall gem parser - - run: echo "::set-output name=tag::$(parse-gemspec-cli *.gemspec | jq .'version')" - name: Set tag version - id: set_tag - - name: Add tag and push - run: | - git tag - git push --tags - - name: Create release - run: gh release create ${{ steps.set_tag.outputs.tag }} --generate-notes + command: "bundle exec rspec spec/system" publish: - needs: release + if: "github.ref == 'refs/heads/master' || github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/heads/release' )" + needs: [tests, system_tests, lint] runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 - - name: Publish to RubyGems - run: | - mkdir -p $HOME/.gem - touch $HOME/.gem/credentials - chmod 0600 $HOME/.gem/credentials - printf -- "---\n:rubygems_api_key: ${GEM_HOST_API_KEY}\n" > $HOME/.gem/credentials - gem build *.gemspec - gem push *.gem - env: - GEM_HOST_API_KEY: "${{secrets.RUBYGEMS_API_KEY}}" \ No newline at end of file + - uses: OpenSourcePolitics/publish-gem-action@master + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + rubygems_api_key: ${{ secrets.RUBYGEMS_API_KEY }} \ No newline at end of file diff --git a/Gemfile b/Gemfile index 8db7554..9c063cd 100644 --- a/Gemfile +++ b/Gemfile @@ -4,7 +4,7 @@ source "https://rubygems.org" ruby RUBY_VERSION -gem "decidim" +gem "decidim", "~> 0.27.0" gem "decidim-cleaner", path: "." gem "bootsnap", "~> 1.4" @@ -12,7 +12,7 @@ gem "puma", ">= 4.3" group :development, :test do gem "byebug", "~> 11.0", platform: :mri - gem "decidim-dev" + gem "decidim-dev", "~> 0.27.0" gem "rubocop-faker" end diff --git a/Gemfile.lock b/Gemfile.lock index 7ec2666..8c3b1cd 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - decidim-cleaner (3.1.0) + decidim-cleaner (3.1.1) decidim-core (~> 0.27.0) GEM @@ -794,15 +794,16 @@ GEM PLATFORMS arm64-darwin-21 + arm64-darwin-22 x86_64-darwin-21 x86_64-linux DEPENDENCIES bootsnap (~> 1.4) byebug (~> 11.0) - decidim + decidim (~> 0.27.0) decidim-cleaner! - decidim-dev + decidim-dev (~> 0.27.0) faker (~> 2.14) letter_opener_web (~> 2.0) listen (~> 3.1) diff --git a/README.md b/README.md index 5520aff..b2a9f0a 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,7 @@ You can then add to your 'config/sidekiq.yml' file: ## Available tasks - [ ] **Delete inactive users** - - Cron task that checks for user accounts where `last_sign_in_at` is superior to environment variable `CLEANER_USER_INACTIVITY_LIMIT`. If true, deletes inactive user from the database. + - Cron task that checks for user accounts where `current_sign_in_at` is superior to environment variable `CLEANER_USER_INACTIVITY_LIMIT`. If true, deletes inactive user from the database. - [ ] **Delete old admin logs** - Cron task that checks for admin logs where `created_at` is anterior to the time you configured in the back office. If true, deletes old admin logs from the database. diff --git a/Rakefile b/Rakefile index 9f1894c..759e8db 100644 --- a/Rakefile +++ b/Rakefile @@ -6,6 +6,9 @@ def install_module(path) Dir.chdir(path) do system("bundle exec rake decidim_cleaner:install:migrations") system("bundle exec rake db:migrate") + system("npm install --save-dev @babel/plugin-proposal-private-methods") + system("npm install --save-dev @babel/plugin-proposal-private-property-in-object") + system("bundle exec rake assets:precompile") end end diff --git a/app/jobs/decidim/cleaner/clean_inactive_users_job.rb b/app/jobs/decidim/cleaner/clean_inactive_users_job.rb index 494ebf8..4c4135e 100644 --- a/app/jobs/decidim/cleaner/clean_inactive_users_job.rb +++ b/app/jobs/decidim/cleaner/clean_inactive_users_job.rb @@ -9,11 +9,11 @@ def perform Decidim::Organization.find_each do |organization| next unless organization.delete_inactive_users? - send_warning(Decidim::User.where(organization: organization) + send_warning(Decidim::User.unscoped.where(organization: organization) .not_deleted .where.not(email: "") - .where("last_sign_in_at < ?", email_inactive_before_date(organization))) - delete_user_and_send_email(Decidim::User.where(organization: organization) + .where("current_sign_in_at < ?", email_inactive_before_date(organization))) + delete_user_and_send_email(Decidim::User.unscoped.where(organization: organization) .not_deleted .where.not(email: "") .where("warning_date < ?", delete_inactive_before_date(organization))) @@ -24,15 +24,20 @@ def send_warning(users) users.find_each do |user| next if user.warning_date.present? - user.update!(warning_date: Time.zone.now) if InactiveUsersMailer.warning_inactive(user).deliver_now - Rails.logger.info "Inactive warning sent to #{user.email}" + if InactiveUsersMailer.warning_inactive(user).deliver_now + user.warning_date = Time.zone.now + user.save(validate: false) + else + Rails.logger.info "Inactive warning sent to #{user.email}" + end end end def delete_user_and_send_email(users) users.find_each do |user| - if user.last_sign_in_at > user.warning_date - user.update!(warning_date: nil) + if user.current_sign_in_at > user.warning_date + user.warning_date = nil + user.save(validate: false) Rails.logger.info "User with id #{user.id} has logged in again, warning date reset" next end diff --git a/config/locales/fi.yml b/config/locales/fi.yml new file mode 100644 index 0000000..4f81c2b --- /dev/null +++ b/config/locales/fi.yml @@ -0,0 +1,40 @@ +--- +fi: + activemodel: + attributes: + organization: + delete_admin_logs: Ota käyttöön hallintatoimintojen lokien poistaminen + delete_admin_logs_after: Hallintatoimintojen lokien säilytysaika (päivää, oletusarvo 365) + delete_inactive_users: Ota käyttöön passiivisten käyttäjätilien poisto + delete_inactive_users_after: Passiivisten käyttäjätilien säilytysaika varoitusviestin lähetyksen jälkeen (päivää, oletusarvo 30) + delete_inactive_users_email_after: Käyttäjätilin poiston varoitusviestin lähetysaika käyttäjätilin ollessa passiivinen (päivää, oletusarvo 365) + decidim: + admin: + menu: + clean: Tietojen puhdistus + organization: + update: + error: Organisaation päivitys epäonnistui. + success: Organisaation päivitys onnistui. + cleaner: + admin: + organization_cleaner: + edit: + update: Päivitä + form: + admin_log_cleaner_title: Hallintatoimintojen lokitiedot + inactive_users_cleaner_title: Passiiviset käyttäjät + delete_reason: Käyttäjä poistettiin käyttäjätilin passiivisuuden takia + inactive_users_mailer: + warning_deletion: + body_1: Käyttäjätilisi on ollut passiivisena %{days} päivää palvelussa %{organization_name} . + body_2: Tämän takia käyttäjätilisi on nyt poistettu palvelusta. + greetings: Terveisin,
%{organization_name}
%{organization_url} + hello: Hei, + subject: Käyttäjätilisi on nyt poistettu + warning_inactive: + body_1: Käyttäjätilisi on ollut passiivisena %{days} päivää palvelussa %{organization_name}. + body_2: Mikäli et kirjaudu alustalle %{remaining_days} päivän aikana, käyttäjätilisi poistetaan. + greetings: Terveisin,
%{organization_name}
%{organization_url} + hello: Hei, + subject: Käyttäjätilisi on ollut passiivisena pitkään diff --git a/lib/decidim/cleaner.rb b/lib/decidim/cleaner.rb index 16470aa..f61c882 100644 --- a/lib/decidim/cleaner.rb +++ b/lib/decidim/cleaner.rb @@ -3,6 +3,7 @@ require "decidim/cleaner/admin" require "decidim/cleaner/engine" require "decidim/cleaner/admin_engine" +require "decidim/cleaner/extends/commands/decidim/destroy_account" module Decidim # This namespace holds the logic of the `Cleaner` module. diff --git a/lib/decidim/cleaner/engine.rb b/lib/decidim/cleaner/engine.rb index aa8cc87..ecb02cb 100644 --- a/lib/decidim/cleaner/engine.rb +++ b/lib/decidim/cleaner/engine.rb @@ -8,6 +8,10 @@ module Cleaner # This is the engine that runs on the public interface of cleaner. class Engine < ::Rails::Engine isolate_namespace Decidim::Cleaner + + config.to_prepare do + Decidim::DestroyAccount.include(Decidim::Cleaner::Extends::DestroyAccount) + end end end end diff --git a/lib/decidim/cleaner/extends/commands/decidim/destroy_account.rb b/lib/decidim/cleaner/extends/commands/decidim/destroy_account.rb new file mode 100644 index 0000000..24df182 --- /dev/null +++ b/lib/decidim/cleaner/extends/commands/decidim/destroy_account.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +module Decidim + module Cleaner + module Extends + # This command destroys the user's account. + module DestroyAccount + extend ActiveSupport::Concern + + included do + private + + # Invalidate all sessions after cleaning Decidim::User record to prevent Active Record error + def destroy_user_account! + @user.name = "" + @user.nickname = "" + @user.email = "" + @user.delete_reason = @form.delete_reason + @user.admin = false if @user.admin? + @user.deleted_at = Time.current + @user.skip_reconfirmation! + @user.avatar.purge + @user.save! + + @user.invalidate_all_sessions! + end + end + end + end + end +end diff --git a/lib/decidim/cleaner/version.rb b/lib/decidim/cleaner/version.rb index 0fa01d1..1cc3ad5 100644 --- a/lib/decidim/cleaner/version.rb +++ b/lib/decidim/cleaner/version.rb @@ -5,7 +5,7 @@ module Decidim # This holds the decidim-meetings version. module Cleaner def self.version - "3.1.0" + "3.1.1" end def self.compatible_decidim_version diff --git a/spec/commands/decidim/destroy_account_spec.rb b/spec/commands/decidim/destroy_account_spec.rb new file mode 100644 index 0000000..1212be9 --- /dev/null +++ b/spec/commands/decidim/destroy_account_spec.rb @@ -0,0 +1,108 @@ +# frozen_string_literal: true + +require "spec_helper" + +module Decidim + describe DestroyAccount do + let(:command) { described_class.new(user, form) } + let(:user) { create(:user, :confirmed) } + let!(:identity) { create(:identity, user: user) } + let(:valid) { true } + let(:data) do + { + delete_reason: "I want to delete my account" + } + end + + let(:form) do + form = double( + delete_reason: data[:delete_reason], + valid?: valid + ) + + form + end + + context "when invalid" do + let(:valid) { false } + + it "broadcasts invalid" do + expect { command.call }.to broadcast(:invalid) + end + end + + context "when valid" do + let(:valid) { true } + + it "broadcasts ok" do + expect { command.call }.to broadcast(:ok) + end + + it "changes the auth salt to invalidate all other sessions" do + old_salt = user.authenticatable_salt + command.call + expect(user.reload.authenticatable_salt).not_to eq(old_salt) + end + + it "stores the deleted_at and delete_reason to the user" do + command.call + expect(user.reload.delete_reason).to eq(data[:delete_reason]) + expect(user.reload.deleted_at).not_to be_nil + end + + it "set name, nickname and email to blank string" do + command.call + expect(user.reload.name).to eq("") + expect(user.reload.nickname).to eq("") + expect(user.reload.email).to eq("") + end + + it "destroys the current user avatar" do + command.call + expect(user.reload.avatar).not_to be_present + end + + it "deletes user's identities" do + expect do + command.call + end.to change(Identity, :count).by(-1) + end + + it "deletes user group memberships" do + user_group = create(:user_group) + create(:user_group_membership, user_group: user_group, user: user) + + expect do + command.call + end.to change(UserGroupMembership, :count).by(-1) + end + + it "deletes the follows" do + other_user = create(:user) + create(:follow, followable: user, user: other_user) + create(:follow, followable: other_user, user: user) + + expect do + command.call + end.to change(Follow, :count).by(-2) + end + + it "deletes participatory space private user" do + create(:participatory_space_private_user, user: user) + + expect do + command.call + end.to change(ParticipatorySpacePrivateUser, :count).by(-1) + end + + context "when user is admin" do + let(:user) { create(:user, :confirmed, :admin) } + + it "removes admin role" do + command.call + expect(user.reload.admin).to be_falsey + end + end + end + end +end diff --git a/spec/jobs/decidim/cleaner/clean_inactive_users_job_spec.rb b/spec/jobs/decidim/cleaner/clean_inactive_users_job_spec.rb index 3fe8aab..6b5d611 100644 --- a/spec/jobs/decidim/cleaner/clean_inactive_users_job_spec.rb +++ b/spec/jobs/decidim/cleaner/clean_inactive_users_job_spec.rb @@ -7,8 +7,8 @@ context "when the delay is specified" do let!(:organization) { create(:organization, delete_inactive_users: true, delete_inactive_users_email_after: 25, delete_inactive_users_after: 5) } - let!(:pending_user) { create(:user, organization: organization, last_sign_in_at: 27.days.ago) } - let!(:inactive_user) { create(:user, organization: organization, last_sign_in_at: 35.days.ago, warning_date: 10.days.ago) } + let!(:pending_user) { create(:user, organization: organization, current_sign_in_at: 27.days.ago) } + let!(:inactive_user) { create(:user, organization: organization, current_sign_in_at: 35.days.ago, warning_date: 10.days.ago) } let!(:user) { create(:user, organization: organization) } it "enqueues job in queue 'cleaner'" do @@ -32,8 +32,8 @@ end context "when users have destroyed his/her account" do - let!(:pending_user) { create(:user, :deleted, organization: organization, last_sign_in_at: 27.days.ago) } - let!(:inactive_user) { create(:user, :deleted, organization: organization, last_sign_in_at: 35.days.ago, warning_date: 10.days.ago) } + let!(:pending_user) { create(:user, :deleted, organization: organization, current_sign_in_at: 27.days.ago) } + let!(:inactive_user) { create(:user, :deleted, organization: organization, current_sign_in_at: 35.days.ago, warning_date: 10.days.ago) } it "doesn't send email" do expect(Decidim::Cleaner::InactiveUsersMailer).not_to receive(:warning_inactive).with(pending_user).and_call_original @@ -53,7 +53,7 @@ end context "when user reconnect after warning" do - let!(:inactive_user) { create(:user, organization: organization, last_sign_in_at: 7.days.ago, warning_date: 10.days.ago) } + let!(:inactive_user) { create(:user, organization: organization, current_sign_in_at: 7.days.ago, warning_date: 10.days.ago) } it "doesn't send email" do expect(Decidim::Cleaner::InactiveUsersMailer).not_to receive(:warning_deletion).with(inactive_user).and_call_original diff --git a/spec/lib/decidim/cleaner/version_spec.rb b/spec/lib/decidim/cleaner/version_spec.rb index adb2de7..5b32f57 100644 --- a/spec/lib/decidim/cleaner/version_spec.rb +++ b/spec/lib/decidim/cleaner/version_spec.rb @@ -7,7 +7,7 @@ module Decidim subject { described_class } it "has version" do - expect(subject.version).to eq("3.1.0") + expect(subject.version).to eq("3.1.1") end it "has decidim version compatibility" do