diff --git a/app/contracts/reminders/base_contract.rb b/app/contracts/reminders/base_contract.rb new file mode 100644 index 000000000000..18c4cd6c11c4 --- /dev/null +++ b/app/contracts/reminders/base_contract.rb @@ -0,0 +1,73 @@ +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2012-2024 the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ + +module Reminders + class BaseContract < ::ModelContract + attribute :creator_id + attribute :remindable_id + attribute :remindable_type + attribute :remind_at + attribute :notes + + validate :validate_creator_exists + validate :validate_acting_user + validate :validate_remindable_exists + validate :validate_manage_reminders_permissions + validate :validate_remind_at_is_in_future + + def self.model = Reminder + + private + + def validate_creator_exists + errors.add :creator, :not_found unless User.exists?(model.creator_id) + end + + def validate_acting_user + errors.add :creator, :invalid unless model.creator_id == user.id + end + + def validate_remindable_exists + errors.add :remindable, :not_found if model.remindable.blank? + end + + def validate_remind_at_is_in_future + if model.remind_at.present? && model.remind_at < Time.current + errors.add :remind_at, :datetime_must_be_in_future + end + end + + def validate_manage_reminders_permissions + return if errors.added?(:remindable, :not_found) + + unless user.allowed_in_project?(:manage_own_reminders, model.remindable.project) + errors.add :base, :error_unauthorized + end + end + end +end diff --git a/app/contracts/reminders/create_contract.rb b/app/contracts/reminders/create_contract.rb new file mode 100644 index 000000000000..6bc3c01eb76a --- /dev/null +++ b/app/contracts/reminders/create_contract.rb @@ -0,0 +1,32 @@ +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2012-2024 the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ + +module Reminders + class CreateContract < BaseContract + end +end diff --git a/app/contracts/reminders/delete_contract.rb b/app/contracts/reminders/delete_contract.rb new file mode 100644 index 000000000000..06895868e6c4 --- /dev/null +++ b/app/contracts/reminders/delete_contract.rb @@ -0,0 +1,36 @@ +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2012-2024 the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ + +module Reminders + class DeleteContract < ::DeleteContract + delete_permission -> { + # The user can delete the reminder if they created it + model.creator_id == user.id + } + end +end diff --git a/config/initializers/permissions.rb b/config/initializers/permissions.rb index 51b4d6184a41..e82b531caf2d 100644 --- a/config/initializers/permissions.rb +++ b/config/initializers/permissions.rb @@ -212,6 +212,11 @@ {}, permissible_on: :project_query, require: :loggedin + + map.permission :manage_own_reminders, + {}, + permissible_on: :project, + require: :member end map.project_module :work_package_tracking, order: 90 do |wpt| diff --git a/config/locales/en.yml b/config/locales/en.yml index 008c15973abf..0b85f143d3c4 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -999,6 +999,7 @@ en: not_an_integer: "is not an integer." not_an_iso_date: "is not a valid date. Required format: YYYY-MM-DD." not_same_project: "doesn't belong to the same project." + datetime_must_be_in_future: "must be in the future." odd: "must be odd." regex_match_failed: "does not match the regular expression %{expression}." regex_invalid: "could not be validated with the associated regular expression." @@ -3490,7 +3491,7 @@ en: If this option is active, login requests will redirect to the configured omniauth provider. The login dropdown and sign-in page will be disabled.
- Note: Unless you also disable password logins, with this option enabled, + Note: Unless you also disable password logins, with this option enabled, users can still log in internally by visiting the %{internal_path} login page. attachments: whitelist_text_html: > diff --git a/spec/contracts/reminders/base_contract_spec.rb b/spec/contracts/reminders/base_contract_spec.rb new file mode 100644 index 000000000000..3005a15155f2 --- /dev/null +++ b/spec/contracts/reminders/base_contract_spec.rb @@ -0,0 +1,123 @@ +# frozen_string_literal: true + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ + +require "spec_helper" +require "contracts/shared/model_contract_shared_context" + +RSpec.describe Reminders::BaseContract do + include_context "ModelContract shared context" + + let(:contract) { described_class.new(reminder, user) } + let(:user) { build_stubbed(:admin) } + let(:creator) { user } + let(:reminder) { build_stubbed(:reminder, creator:) } + + before do + User.current = user + allow(User).to receive(:exists?).with(user.id).and_return(true) + end + + describe "admin user" do + it_behaves_like "contract is valid" + end + + describe "non-admin user" do + context "with valid permissions" do + let(:user) { build_stubbed(:user) } + + before do + mock_permissions_for(user) do |mock| + mock.allow_in_project(:manage_own_reminders, project: reminder.remindable.project) + end + end + + it_behaves_like "contract is valid" + end + + context "without valid permissions" do + let(:user) { build_stubbed(:user) } + + it_behaves_like "contract is invalid", base: :error_unauthorized + end + end + + describe "validate creator exists" do + context "when creator does not exist" do + before { allow(User).to receive(:exists?).with(user.id).and_return(false) } + + it_behaves_like "contract is invalid", creator: :not_found + end + end + + describe "validate acting user" do + context "when the current user is different from the remindable acting user" do + let(:different_user) { build_stubbed(:user) } + + before do + allow(User).to receive(:exists?).with(different_user.id).and_return(true) + reminder.creator = different_user + end + + it_behaves_like "contract is invalid", creator: :invalid + end + end + + describe "validate remindable object" do + context "when remindable is blank" do + before { reminder.remindable = nil } + + it_behaves_like "contract is invalid", remindable: :not_found + end + + context "when remindable is a work package" do + let(:work_package) { build_stubbed(:work_package) } + + before { reminder.remindable = work_package } + + it_behaves_like "contract is valid" + end + end + + describe "validate remind at is in future" do + context "when remind at is in the past" do + before { reminder.remind_at = 1.day.ago } + + it_behaves_like "contract is invalid", remind_at: :datetime_must_be_in_future + end + + context "when remind at is in the future" do + before { reminder.remind_at = 1.day.from_now } + + it_behaves_like "contract is valid" + end + end + + include_examples "contract reuses the model errors" +end diff --git a/spec/contracts/reminders/delete_contract_spec.rb b/spec/contracts/reminders/delete_contract_spec.rb new file mode 100644 index 000000000000..2d884059023b --- /dev/null +++ b/spec/contracts/reminders/delete_contract_spec.rb @@ -0,0 +1,48 @@ +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ + +require "spec_helper" +require "contracts/shared/model_contract_shared_context" + +RSpec.describe Reminders::DeleteContract do + include_context "ModelContract shared context" + + let(:contract) { described_class.new(reminder, current_user) } + let(:current_user) { build_stubbed(:admin) } + let(:reminder) { build_stubbed(:reminder, creator: current_user) } + + context "when user is different from the one that created the reminder" do + let(:another_user) { build_stubbed(:admin) } + + before { reminder.creator = another_user } + + it_behaves_like "contract user is unauthorized" + end + + include_examples "contract reuses the model errors" +end diff --git a/spec/factories/reminders_factory.rb b/spec/factories/reminders_factory.rb new file mode 100644 index 000000000000..95e55d5e7ad8 --- /dev/null +++ b/spec/factories/reminders_factory.rb @@ -0,0 +1,36 @@ +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ + +FactoryBot.define do + factory :reminder do + remindable factory: :work_package_journal + creator factory: :user + remind_at { 1.day.from_now } + notes { "This is a reminder" } + end +end