Skip to content
Snippets Groups Projects
Commit ec0061a9 authored by Rémy Coutable's avatar Rémy Coutable
Browse files

Allow Member.add_user to handle access requesters


Changes include:

- Ensure Member.add_user is not called directly when not necessary
- New GroupMember.add_users_to_group to have the same abstraction level as for Project
- Refactor Member.add_user to take a source instead of an array of members
- Fix Rubocop offenses
- Always use Project#add_user instead of project.team.add_user
- Factorize users addition as members in Member.add_users_to_source
- Make access_level a keyword argument in GroupMember.add_users_to_group and ProjectMember.add_users_to_projects
- Destroy any requester before adding them as a member
- Improve the way we handle access requesters in Member.add_user
  Instead of removing the requester and creating a new member,
  we now simply accepts their access request. This way, they will
  receive a "access request granted" email.
- Fix error that was previously silently ignored
- Stop raising when access level is invalid in Member, let Rails validation do their work

Signed-off-by: default avatarRémy Coutable <remy@rymai.me>
parent 3b206ccb
No related branches found
No related tags found
No related merge requests found
Loading
Loading
@@ -74,22 +74,17 @@ describe Member, models: true do
@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 = create(:project_member, :developer,
project: project,
invite_token: '1234',
invite_email: 'toto1@example.com')
 
accepted_invite_user = build(:user, state: :active)
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 = create(:project_member, :developer,
project: project,
invite_token: '1234',
invite_email: 'toto2@example.com').
tap { |u| u.accept_invite!(accepted_invite_user) }
 
requested_user = create(:user).tap { |u| project.request_access(u) }
@requested_member = project.requesters.find_by(user_id: requested_user.id)
Loading
Loading
@@ -176,39 +171,209 @@ describe Member, models: true do
it { is_expected.to respond_to(:user_email) }
end
 
describe ".add_user" do
let!(:user) { create(:user) }
let(:project) { create(:project) }
describe '.add_user' do
%w[project group].each do |source_type|
context "when source is a #{source_type}" do
let!(:source) { create(source_type) }
let!(:user) { create(:user) }
let!(:admin) { create(:admin) }
 
context "when called with a user id" do
it "adds the user as a member" do
Member.add_user(project.project_members, user.id, ProjectMember::MASTER)
it 'returns a <Source>Member object' do
member = described_class.add_user(source, user, :master)
 
expect(project.users).to include(user)
end
end
expect(member).to be_a "#{source_type.classify}Member".constantize
expect(member).to be_persisted
end
 
context "when called with a user object" do
it "adds the user as a member" do
Member.add_user(project.project_members, user, ProjectMember::MASTER)
it 'sets members.created_by to the given current_user' do
member = described_class.add_user(source, user, :master, current_user: admin)
 
expect(project.users).to include(user)
end
end
expect(member.created_by).to eq(admin)
end
 
context "when called with a known user email" do
it "adds the user as a member" do
Member.add_user(project.project_members, user.email, ProjectMember::MASTER)
it 'sets members.expires_at to the given expires_at' do
member = described_class.add_user(source, user, :master, expires_at: Date.new(2016, 9, 22))
 
expect(project.users).to include(user)
end
end
expect(member.expires_at).to eq(Date.new(2016, 9, 22))
end
described_class.access_levels.each do |sym_key, int_access_level|
it "accepts the :#{sym_key} symbol as access level" do
expect(source.users).not_to include(user)
member = described_class.add_user(source, user.id, sym_key)
expect(member.access_level).to eq(int_access_level)
expect(source.users.reload).to include(user)
end
it "accepts the #{int_access_level} integer as access level" do
expect(source.users).not_to include(user)
member = described_class.add_user(source, user.id, int_access_level)
expect(member.access_level).to eq(int_access_level)
expect(source.users.reload).to include(user)
end
end
context 'with no current_user' do
context 'when called with a known user id' do
it 'adds the user as a member' do
expect(source.users).not_to include(user)
described_class.add_user(source, user.id, :master)
expect(source.users.reload).to include(user)
end
end
context 'when called with an unknown user id' do
it 'adds the user as a member' do
expect(source.users).not_to include(user)
described_class.add_user(source, 42, :master)
expect(source.users.reload).not_to include(user)
end
end
context 'when called with a user object' do
it 'adds the user as a member' do
expect(source.users).not_to include(user)
described_class.add_user(source, user, :master)
expect(source.users.reload).to include(user)
end
end
context 'when called with a requester user object' do
before do
source.request_access(user)
end
it 'adds the requester as a member' do
expect(source.users).not_to include(user)
expect(source.requesters.exists?(user_id: user)).to be_truthy
expect { described_class.add_user(source, user, :master) }.
to raise_error(Gitlab::Access::AccessDeniedError)
expect(source.users.reload).not_to include(user)
expect(source.requesters.reload.exists?(user_id: user)).to be_truthy
end
end
context 'when called with a known user email' do
it 'adds the user as a member' do
expect(source.users).not_to include(user)
described_class.add_user(source, user.email, :master)
expect(source.users.reload).to include(user)
end
end
context 'when called with an unknown user email' do
it 'creates an invited member' do
expect(source.users).not_to include(user)
described_class.add_user(source, 'user@example.com', :master)
expect(source.members.invite.pluck(:invite_email)).to include('user@example.com')
end
end
end
context 'when current_user can update member' do
it 'creates the member' do
expect(source.users).not_to include(user)
described_class.add_user(source, user, :master, current_user: admin)
expect(source.users.reload).to include(user)
end
context 'when called with a requester user object' do
before do
source.request_access(user)
end
it 'adds the requester as a member' do
expect(source.users).not_to include(user)
expect(source.requesters.exists?(user_id: user)).to be_truthy
described_class.add_user(source, user, :master, current_user: admin)
expect(source.users.reload).to include(user)
expect(source.requesters.reload.exists?(user_id: user)).to be_falsy
end
end
end
context 'when current_user cannot update member' do
it 'does not create the member' do
expect(source.users).not_to include(user)
member = described_class.add_user(source, user, :master, current_user: user)
expect(source.users.reload).not_to include(user)
expect(member).not_to be_persisted
end
context 'when called with a requester user object' do
before do
source.request_access(user)
end
it 'does not destroy the requester' do
expect(source.users).not_to include(user)
expect(source.requesters.exists?(user_id: user)).to be_truthy
described_class.add_user(source, user, :master, current_user: user)
expect(source.users.reload).not_to include(user)
expect(source.requesters.exists?(user_id: user)).to be_truthy
end
end
end
context 'when member already exists' do
before do
source.add_user(user, :developer)
end
context 'with no current_user' do
it 'updates the member' do
expect(source.users).to include(user)
described_class.add_user(source, user, :master)
expect(source.members.find_by(user_id: user).access_level).to eq(Gitlab::Access::MASTER)
end
end
context 'when current_user can update member' do
it 'updates the member' do
expect(source.users).to include(user)
described_class.add_user(source, user, :master, current_user: admin)
expect(source.members.find_by(user_id: user).access_level).to eq(Gitlab::Access::MASTER)
end
end
context 'when current_user cannot update member' do
it 'does not update the member' do
expect(source.users).to include(user)
 
context "when called with an unknown user email" do
it "adds a member invite" do
Member.add_user(project.project_members, "user@example.com", ProjectMember::MASTER)
described_class.add_user(source, user, :master, current_user: user)
 
expect(project.project_members.invite.pluck(:invite_email)).to include("user@example.com")
expect(source.members.find_by(user_id: user).access_level).to eq(Gitlab::Access::DEVELOPER)
end
end
end
end
end
end
Loading
Loading
require 'spec_helper'
 
describe GroupMember, models: true do
describe '.access_level_roles' do
it 'returns Gitlab::Access.options_with_owner' do
expect(described_class.access_level_roles).to eq(Gitlab::Access.options_with_owner)
end
end
describe '.access_levels' do
it 'returns Gitlab::Access.options_with_owner' do
expect(described_class.access_levels).to eq(Gitlab::Access.sym_options_with_owner)
end
end
describe '.add_users_to_group' do
it 'adds the given users to the given group' do
group = create(:group)
users = create_list(:user, 2)
described_class.add_users_to_group(
group,
[users.first.id, users.second],
described_class::MASTER
)
expect(group.users).to include(users.first, users.second)
end
end
describe 'notifications' do
describe "#after_create" do
it "sends email to user" do
Loading
Loading
Loading
Loading
@@ -15,6 +15,26 @@ describe ProjectMember, models: true do
it { is_expected.to include_module(Gitlab::ShellAdapter) }
end
 
describe '.access_level_roles' do
it 'returns Gitlab::Access.options' do
expect(described_class.access_level_roles).to eq(Gitlab::Access.options)
end
end
describe '.add_user' do
context 'when called with the project owner' do
it 'adds the user as a member' do
project = create(:empty_project)
expect(project.users).not_to include(project.owner)
described_class.add_user(project, project.owner, :master, current_user: project.owner)
expect(project.users.reload).to include(project.owner)
end
end
end
describe '#real_source_type' do
subject { create(:project_member).real_source_type }
 
Loading
Loading
@@ -50,7 +70,7 @@ describe ProjectMember, models: true do
end
end
 
describe :import_team do
describe '.import_team' do
before do
@project_1 = create :project
@project_2 = create :project
Loading
Loading
@@ -81,25 +101,21 @@ describe ProjectMember, models: true do
end
 
describe '.add_users_to_projects' do
before do
@project_1 = create :project
@project_2 = create :project
it 'adds the given users to the given projects' do
projects = create_list(:empty_project, 2)
users = create_list(:user, 2)
 
@user_1 = create :user
@user_2 = create :user
ProjectMember.add_users_to_projects(
[@project_1.id, @project_2.id],
[@user_1.id, @user_2.id],
ProjectMember::MASTER
)
end
described_class.add_users_to_projects(
[projects.first.id, projects.second],
[users.first.id, users.second],
described_class::MASTER)
 
it { expect(@project_1.users).to include(@user_1) }
it { expect(@project_1.users).to include(@user_2) }
expect(projects.first.users).to include(users.first)
expect(projects.first.users).to include(users.second)
 
it { expect(@project_2.users).to include(@user_1) }
it { expect(@project_2.users).to include(@user_2) }
expect(projects.second.users).to include(users.first)
expect(projects.second.users).to include(users.second)
end
end
 
describe '.truncate_teams' do
Loading
Loading
Loading
Loading
@@ -836,7 +836,7 @@ describe Project, models: true do
 
describe 'when a user has access to a project' do
before do
project.team.add_user(user, Gitlab::Access::MASTER)
project.add_user(user, Gitlab::Access::MASTER)
end
 
it { is_expected.to eq([project]) }
Loading
Loading
Loading
Loading
@@ -15,7 +15,7 @@ describe API::API, api: true do
let(:milestone) { create(:milestone, title: '1.0.0', project: project) }
 
before do
project.team << [user, :reporters]
project.team << [user, :reporter]
end
 
describe "GET /projects/:id/merge_requests" do
Loading
Loading
@@ -299,7 +299,7 @@ describe API::API, api: true do
let!(:unrelated_project) { create(:project, namespace: create(:user).namespace, creator_id: user2.id) }
 
before :each do |each|
fork_project.team << [user2, :reporters]
fork_project.team << [user2, :reporter]
end
 
it "returns merge_request" do
Loading
Loading
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment