From f7335dc8d96ade2f3a711a496723ca49dfdbc4a3 Mon Sep 17 00:00:00 2001 From: Rachael Wright-Munn Date: Thu, 6 Jun 2024 18:30:27 +0000 Subject: [PATCH] Friendship overrides other configured permissions (#228) * Ensure friendship takes precendence over other permissions. If a profile has marked another profile as "friendly", then they want to befriend that profile, and we should prioritize that over any other permission constraints they've placed such as confirmed emails, etc. With the exception of myself - where friends are explicitly excluded. * Modify RSpec config in spec_helper. * Allow :focus to be used in tests * Use documentation format automatically when one file is run --- app/models/profile.rb | 5 +- app/policies/profile_policy.rb | 7 +-- spec/models/profile_spec.rb | 31 ++++++++++++ spec/policies/profile_policy_spec.rb | 74 ++++++++++++++++++++++++++++ spec/spec_helper.rb | 37 +++++++------- 5 files changed, 132 insertions(+), 22 deletions(-) diff --git a/app/models/profile.rb b/app/models/profile.rb index 40196e9..8674ea3 100644 --- a/app/models/profile.rb +++ b/app/models/profile.rb @@ -17,7 +17,10 @@ class Profile < ApplicationRecord scope :with_authenticated, -> { where(visibility: %i[everyone authenticated]) } scope :nonblocked, ->(profile) { where.not(id: Friendship.blocks(profile).select(:buddy_id)) } - scope :befriended, ->(profile) { where(id: Friendship.friends_of(profile).select(:buddy_id), visibility: :friends) } + scope :befriended, lambda { |profile| + where(id: Friendship.friends_of(profile).select(:buddy_id)) + .where.not(visibility: :myself) + } # Relationships belongs_to :user diff --git a/app/policies/profile_policy.rb b/app/policies/profile_policy.rb index 1974edd..dd1327d 100644 --- a/app/policies/profile_policy.rb +++ b/app/policies/profile_policy.rb @@ -16,7 +16,7 @@ def index? # Whether the user can view the Profile's Handle, Bio, and Avatar. def show? return true if mine? || admin? || profile.visible_to_everyone? - confirmed_user? + confirmed_user? || profile.friends_with?(current_profile) end # This method controls whether a user can view a profile's details. @@ -29,8 +29,9 @@ def show? # * Myself - only the user can view - NOT EVEN EXISTING FRIENDS! def show_details? return true if mine? || admin? || profile.visible_to_everyone? - return confirmed_user? if profile.visible_to_authenticated? - profile.friends_with? current_profile if profile.visible_to_friends? + return false if profile.visible_to_myself? + return true if profile.friends_with? current_profile + confirmed_user? if profile.visible_to_authenticated? end def create? diff --git a/spec/models/profile_spec.rb b/spec/models/profile_spec.rb index 0d8ec3d..7576309 100644 --- a/spec/models/profile_spec.rb +++ b/spec/models/profile_spec.rb @@ -34,4 +34,35 @@ it { is_expected.to be_truthy } end end + + describe "#befriended" do + subject { described_class.befriended(profile) } + + context "with 'authenticated' visibility friend" do + let(:authenticated_profile) { create :profile, :authenticated } + let!(:friendship) { create :friendship, buddy: authenticated_profile, friend: profile, status: :accepted } + + it "does include" do + expect(subject).to include authenticated_profile + end + end + + context "with 'friends' visibility friend" do + let(:friends_profile) { create :profile, :friends } + let!(:friendship) { create :friendship, buddy: friends_profile, friend: profile, status: :accepted } + + it "does include" do + expect(subject).to include friends_profile + end + end + + context "with 'myself' visibility friend" do + let(:myself_profile) { create :profile, :myself } + let!(:friendship) { create :friendship, buddy: myself_profile, friend: profile, status: :accepted } + + it "does not include" do + expect(subject).not_to include myself_profile + end + end + end end diff --git a/spec/policies/profile_policy_spec.rb b/spec/policies/profile_policy_spec.rb index 885cf15..fc7d76e 100644 --- a/spec/policies/profile_policy_spec.rb +++ b/spec/policies/profile_policy_spec.rb @@ -23,6 +23,67 @@ end end + permissions :show? do + context "with viewing everyone profile" do + let(:profile) { build :profile, visibility: :everyone } + + context "with no user" do + it { expect(described_class).to permit(nil, profile) } + end + + context "with unconfirmed user" do + let(:user) { create :user, :unconfirmed } + + it { expect(described_class).to permit(user, profile) } + end + + context "with confirmed user" do + let(:user) { create :user } + + it { expect(described_class).to permit(user, profile) } + end + + context "with admin" do + let(:user) { create :user, :admin } + + it { expect(described_class).to permit(user, profile) } + end + end + + context "when viewing authenticated profile" do + let(:profile) { build :profile, visibility: :authenticated } + + context "with no user" do + it { expect(described_class).not_to permit(nil, profile) } + end + + context "with unconfirmed user" do + let(:user) { create :user, :unconfirmed } + + it { expect(described_class).not_to permit(user, profile) } + + context "when friends" do + let!(:user_profile) { create :profile, user: } + let!(:friendship) { create :friendship, buddy: profile, friend: user.profile, status: :accepted } + + it { expect(described_class).to permit(user, profile) } + end + end + + context "with confirmed user" do + let(:user) { create :user } + + it { expect(described_class).to permit(user, profile) } + end + + context "with admin" do + let(:user) { create :user, :admin } + + it { expect(described_class).to permit(user, profile) } + end + end + end + permissions :show_details? do context "with viewing everyone profile" do let(:profile) { build :profile, visibility: :everyone } @@ -61,6 +122,13 @@ let(:user) { create :user, :unconfirmed } it { expect(described_class).not_to permit(user, profile) } + + context "when friends" do + let!(:user_profile) { create :profile, user: } + let!(:friendship) { create :friendship, buddy: profile, friend: user.profile, status: :accepted } + + it { expect(described_class).to permit(user, profile) } + end end context "with confirmed user" do @@ -193,6 +261,12 @@ it { is_expected.to match_array %w[everyone myself] } end + context "when authenticated profile is friendly with you" do + let!(:friendship) { create :friendship, buddy: authenticated, friend: current_profile, status: :accepted } + + it { is_expected.to match_array %w[everyone authenticated myself] } + end + context "when you are blocked" do let!(:blocked_by_friends) { create :friendship, buddy: friends, friend: current_profile, status: :blocked } let!(:blocked_by_everyone) { create :friendship, buddy: everyone, friend: current_profile, status: :blocked } diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 01f7c97..4d5e486 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -48,13 +48,14 @@ # The settings below are suggested to provide a good initial experience # with RSpec, but feel free to customize to your heart's content. - # # This allows you to limit a spec run to individual examples or groups - # # you care about by tagging them with `:focus` metadata. When nothing - # # is tagged with `:focus`, all examples get run. RSpec also provides - # # aliases for `it`, `describe`, and `context` that include `:focus` - # # metadata: `fit`, `fdescribe` and `fcontext`, respectively. - # config.filter_run_when_matching :focus - # + + # This allows you to limit a spec run to individual examples or groups + # you care about by tagging them with `:focus` metadata. When nothing + # is tagged with `:focus`, all examples get run. RSpec also provides + # aliases for `it`, `describe`, and `context` that include `:focus` + # metadata: `fit`, `fdescribe` and `fcontext`, respectively. + config.filter_run_when_matching :focus + # # Allows RSpec to persist some state between runs in order to support # # the `--only-failures` and `--next-failure` CLI options. We recommend # # you configure your source control system to ignore this file. @@ -66,17 +67,17 @@ # # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ # # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode # config.disable_monkey_patching! - # - # # Many RSpec users commonly either run the entire suite or an individual - # # file, and it's useful to allow more verbose output when running an - # # individual spec file. - # if config.files_to_run.one? - # # Use the documentation formatter for detailed output, - # # unless a formatter has already been configured - # # (e.g. via a command-line flag). - # config.default_formatter = "doc" - # end - # + + # Many RSpec users commonly either run the entire suite or an individual + # file, and it's useful to allow more verbose output when running an + # individual spec file. + if config.files_to_run.one? + # Use the documentation formatter for detailed output, + # unless a formatter has already been configured + # (e.g. via a command-line flag). + config.default_formatter = "doc" + end + # # Print the 10 slowest examples and example groups at the # # end of the spec run, to help surface which specs are running # # particularly slow.