Commit 2edbafab authored by Douwe Maan's avatar Douwe Maan Committed by Rémy Coutable

Merge branch '21650-only-active-users-can-be-members' into 'master'

Exclude some pending or inactivated rows in Member scopes

An unapproved request or not-yet-accepted invite should not give access rights. Neither should a blocked user be considered a member of anything.

One visible outcome of this behaviour is that owners and masters of a group or project may be blocked, yet still receive notification emails for access requests.

Closes https://gitlab.com/gitlab-org/gitlab-ce/issues/21650

See merge request !1994
Signed-off-by: default avatarRémy Coutable <remy@rymai.me>
parent 9df6a229
Please view this file on the master branch, on stable branches it's out of date. Please view this file on the master branch, on stable branches it's out of date.
v 8.10.9
- Exclude some pending or inactivated rows in Member scopes.
v 8.10.8 v 8.10.8
- Fix broken gitlab:backup:restore because of bad permissions on repo storage !6098 (Dirk Hörner) - Fix broken gitlab:backup:restore because of bad permissions on repo storage !6098 (Dirk Hörner)
......
...@@ -27,17 +27,34 @@ class Member < ActiveRecord::Base ...@@ -27,17 +27,34 @@ class Member < ActiveRecord::Base
allow_nil: true allow_nil: true
} }
# This scope encapsulates (most of) the conditions a row in the member table
# must satisfy if it is a valid permission. Of particular note:
#
# * Access requests must be excluded
# * Blocked users must be excluded
# * Invitations take effect immediately
# * expires_at is not implemented. A background worker purges expired rows
scope :active, -> do
is_external_invite = arel_table[:user_id].eq(nil).and(arel_table[:invite_token].not_eq(nil))
user_is_active = User.arel_table[:state].eq(:active)
includes(:user).references(:users)
.where(is_external_invite.or(user_is_active))
.where(requested_at: nil)
end
scope :invite, -> { where.not(invite_token: nil) } scope :invite, -> { where.not(invite_token: nil) }
scope :non_invite, -> { where(invite_token: nil) } scope :non_invite, -> { where(invite_token: nil) }
scope :request, -> { where.not(requested_at: nil) } scope :request, -> { where.not(requested_at: nil) }
scope :has_access, -> { where('access_level > 0') }
scope :has_access, -> { active.where('access_level > 0') }
scope :guests, -> { where(access_level: GUEST) }
scope :reporters, -> { where(access_level: REPORTER) } scope :guests, -> { active.where(access_level: GUEST) }
scope :developers, -> { where(access_level: DEVELOPER) } scope :reporters, -> { active.where(access_level: REPORTER) }
scope :masters, -> { where(access_level: MASTER) } scope :developers, -> { active.where(access_level: DEVELOPER) }
scope :owners, -> { where(access_level: OWNER) } scope :masters, -> { active.where(access_level: MASTER) }
scope :owners_and_masters, -> { where(access_level: [OWNER, MASTER]) } scope :owners, -> { active.where(access_level: OWNER) }
scope :owners_and_masters, -> { active.where(access_level: [OWNER, MASTER]) }
before_validation :generate_invite_token, on: :create, if: -> (member) { member.invite_email.present? } before_validation :generate_invite_token, on: :create, if: -> (member) { member.invite_email.present? }
......
...@@ -57,7 +57,7 @@ describe Member, models: true do ...@@ -57,7 +57,7 @@ describe Member, models: true do
describe 'Scopes & finders' do describe 'Scopes & finders' do
before do before do
project = create(:project) project = create(:empty_project)
group = create(:group) group = create(:group)
@owner_user = create(:user).tap { |u| group.add_owner(u) } @owner_user = create(:user).tap { |u| group.add_owner(u) }
@owner = group.members.find_by(user_id: @owner_user.id) @owner = group.members.find_by(user_id: @owner_user.id)
...@@ -65,11 +65,30 @@ describe Member, models: true do ...@@ -65,11 +65,30 @@ describe Member, models: true do
@master_user = create(:user).tap { |u| project.team << [u, :master] } @master_user = create(:user).tap { |u| project.team << [u, :master] }
@master = project.members.find_by(user_id: @master_user.id) @master = project.members.find_by(user_id: @master_user.id)
ProjectMember.add_user(project.members, 'toto1@example.com', Gitlab::Access::DEVELOPER, @master_user) @blocked_user = create(:user).tap do |u|
project.team << [u, :master]
project.team << [u, :developer]
u.block!
end
@blocked_master = project.members.find_by(user_id: @blocked_user.id, access_level: Gitlab::Access::MASTER)
@blocked_developer = project.members.find_by(user_id: @blocked_user.id, access_level: Gitlab::Access::DEVELOPER)
Member.add_user(
project.members,
'toto1@example.com',
Gitlab::Access::DEVELOPER,
current_user: @master_user
)
@invited_member = project.members.invite.find_by_invite_email('toto1@example.com') @invited_member = project.members.invite.find_by_invite_email('toto1@example.com')
accepted_invite_user = build(:user) accepted_invite_user = build(:user, state: :active)
ProjectMember.add_user(project.members, 'toto2@example.com', Gitlab::Access::DEVELOPER, @master_user) Member.add_user(
project.members,
'toto2@example.com',
Gitlab::Access::DEVELOPER,
current_user: @master_user
)
@accepted_invite_member = project.members.invite.find_by_invite_email('toto2@example.com').tap { |u| u.accept_invite!(accepted_invite_user) } @accepted_invite_member = project.members.invite.find_by_invite_email('toto2@example.com').tap { |u| u.accept_invite!(accepted_invite_user) }
requested_user = create(:user).tap { |u| project.request_access(u) } requested_user = create(:user).tap { |u| project.request_access(u) }
...@@ -103,6 +122,19 @@ describe Member, models: true do ...@@ -103,6 +122,19 @@ describe Member, models: true do
it { expect(described_class.request).not_to include @accepted_request_member } it { expect(described_class.request).not_to include @accepted_request_member }
end end
describe '.developers' do
subject { described_class.developers.to_a }
it { is_expected.not_to include @owner }
it { is_expected.not_to include @master }
it { is_expected.to include @invited_member }
it { is_expected.to include @accepted_invite_member }
it { is_expected.not_to include @requested_member }
it { is_expected.to include @accepted_request_member }
it { is_expected.not_to include @blocked_master }
it { is_expected.not_to include @blocked_developer }
end
describe '.owners_and_masters' do describe '.owners_and_masters' do
it { expect(described_class.owners_and_masters).to include @owner } it { expect(described_class.owners_and_masters).to include @owner }
it { expect(described_class.owners_and_masters).to include @master } it { expect(described_class.owners_and_masters).to include @master }
...@@ -110,6 +142,20 @@ describe Member, models: true do ...@@ -110,6 +142,20 @@ describe Member, models: true do
it { expect(described_class.owners_and_masters).not_to include @accepted_invite_member } it { expect(described_class.owners_and_masters).not_to include @accepted_invite_member }
it { expect(described_class.owners_and_masters).not_to include @requested_member } it { expect(described_class.owners_and_masters).not_to include @requested_member }
it { expect(described_class.owners_and_masters).not_to include @accepted_request_member } it { expect(described_class.owners_and_masters).not_to include @accepted_request_member }
it { expect(described_class.owners_and_masters).not_to include @blocked_master }
end
describe '.has_access' do
subject { described_class.has_access.to_a }
it { is_expected.to include @owner }
it { is_expected.to include @master }
it { is_expected.to include @invited_member }
it { is_expected.to include @accepted_invite_member }
it { is_expected.not_to include @requested_member }
it { is_expected.to include @accepted_request_member }
it { is_expected.not_to include @blocked_master }
it { is_expected.not_to include @blocked_developer }
end end
end end
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment