Commit 5fcbee2d authored by Vijay Hawoldar's avatar Vijay Hawoldar

Add member approval service

When namespace level user caps are enabled and the limit has been
reached, new members will be put into an awaiting state. This service
will handle approving/activating them.

Changelog: added
EE: true
parent 86093d5c
# frozen_string_literal: true
# Members added to groups and projects after the root group user cap has been reached
# will be added in an `awaiting` state.
#
# Root Group owners may activate those members at their discretion via this service, either
# individually or all awaiting members.
#
# User facing terminology differs to what we use in the backend:
#
# - activate => approve
# - awaiting => pending
module Members
class ActivateService
include BaseServiceUtility
def initialize(group, user: nil, activate_all: false, current_user:)
@group = group
@user = user
@current_user = current_user
@activate_all = activate_all
end
def execute
return error(_('No group provided')) unless group
return error(_('You do not have permission to approve a member'), :forbidden) unless allowed?
if activate_memberships
log_event
success
else
error(_('No memberships found'), :bad_request)
end
end
private
attr_reader :current_user, :group, :user, :activate_all
def activate_memberships
memberships_found = false
memberships = activate_all ? awaiting_memberships : user_memberships
memberships.find_each do |member|
memberships_found = true
member.activate
end
memberships_found
end
# rubocop: disable CodeReuse/ActiveRecord
def user_memberships
awaiting_memberships.where(user_id: user.id)
end
# rubocop: enable CodeReuse/ActiveRecord
def awaiting_memberships
::Member.in_hierarchy(group).awaiting
end
def allowed?
can?(current_user, :admin_group_member, group)
end
def log_event
log_params = {
group: group.id,
approved_by: current_user.id
}.tap do |params|
params[:message] = activate_all ? 'Approved all pending group members' : 'Group member access approved'
params[:user] = user.id unless activate_all
end
Gitlab::AppLogger.info(log_params)
end
end
end
# frozen_string_literal: true
FactoryBot.modify do
factory :group_member do
trait :awaiting do
after(:create) do |member|
member.wait
end
end
end
end
# frozen_string_literal: true
FactoryBot.modify do
factory :project_member do
trait :awaiting do
after(:create) do |member|
member.wait
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Members::ActivateService do
describe '#execute' do
let_it_be(:current_user) { create(:user) }
let_it_be(:user) { create(:user) }
let_it_be(:root_group) { create(:group) }
let_it_be(:project) { create(:project, group: root_group) }
let_it_be(:sub_group) { create(:group, parent: root_group) }
let(:group) { root_group }
let(:activate_all) { false }
subject(:execute) { described_class.new(group, user: user, current_user: current_user, activate_all: activate_all).execute }
context 'when unauthorized' do
it 'returns an access error' do
result = execute
expect(result[:status]).to eq :error
expect(result[:message]).to eq 'You do not have permission to approve a member'
end
end
context 'when no group is provided' do
let(:group) { nil }
it 'returns an error' do
result = execute
expect(result[:status]).to eq :error
expect(result[:message]).to eq 'No group provided'
end
end
shared_examples 'successful user activation' do
before do
expect(member.awaiting?).to be true
end
it 'activates the member' do
expect(execute[:status]).to eq :success
expect(member.reload.active?).to be true
end
it 'logs the approval in application logs' do
expect(Gitlab::AppLogger).to receive(:info).with(
message: "Group member access approved",
group: group.id,
user: user.id,
approved_by: current_user.id
)
execute
end
end
context 'when authorized' do
before do
group.add_owner(current_user)
end
context 'when activating an individual user' do
context 'when user is an awaiting member of a root group' do
it_behaves_like 'successful user activation' do
let(:member) { create(:group_member, :awaiting, group: root_group, user: user) }
end
end
context 'when user is an awaiting member of a sub-group' do
let(:group) { sub_group }
it_behaves_like 'successful user activation' do
let(:member) { create(:group_member, :awaiting, group: sub_group, user: user) }
end
end
context 'when user is an awaiting member of a project' do
it_behaves_like 'successful user activation' do
let(:member) { create(:project_member, :awaiting, project: project, user: user) }
end
end
context 'when user is not an awaiting member' do
it 'returns an error' do
result = execute
expect(result[:status]).to eq :error
expect(result[:message]).to eq 'No memberships found'
end
end
end
context 'when activating all awaiting members' do
let!(:group_members) { create_list(:group_member, 5, :awaiting, group: group) }
let!(:sub_group_members) { create_list(:group_member, 5, :awaiting, group: sub_group) }
let!(:project_members) { create_list(:project_member, 5, :awaiting, project: project) }
let(:user) { nil }
let(:activate_all) { true }
it 'activates all awaiting group members' do
execute
group_members.each do |member|
expect(member.reload.active?).to be true
end
end
it 'activates all awaiting sub_group members' do
execute
sub_group_members.each do |member|
expect(member.reload.active?).to be true
end
end
it 'activates all awaiting project members' do
execute
project_members.each do |member|
expect(member.reload.active?).to be true
end
end
it 'logs the approval in application logs' do
expect(Gitlab::AppLogger).to receive(:info).with(
message: "Approved all pending group members",
group: group.id,
approved_by: current_user.id
)
execute
end
end
end
end
end
......@@ -23326,6 +23326,9 @@ msgstr ""
msgid "No forks are available to you."
msgstr ""
msgid "No group provided"
msgstr ""
msgid "No grouping"
msgstr ""
......@@ -23377,6 +23380,9 @@ msgstr ""
msgid "No members found"
msgstr ""
msgid "No memberships found"
msgstr ""
msgid "No merge requests found"
msgstr ""
......@@ -39464,6 +39470,9 @@ msgstr ""
msgid "You do not have permission to access dora metrics."
msgstr ""
msgid "You do not have permission to approve a member"
msgstr ""
msgid "You do not have permission to leave this %{namespaceType}."
msgstr ""
......
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