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