Commit c9d3e7b8 authored by Imre Farkas's avatar Imre Farkas

Group authorization refresh to consider shared groups

When a shared group creates a new project, that should be visible to
members of the shared with group too. We need to include those members
in Group#user_ids_for_project_authorizations.
parent 04a60057
...@@ -325,15 +325,17 @@ class Group < Namespace ...@@ -325,15 +325,17 @@ class Group < Namespace
def members_with_parents def members_with_parents
# Avoids an unnecessary SELECT when the group has no parents # Avoids an unnecessary SELECT when the group has no parents
source_ids = source_ids =
if parent_id if has_parent?
self_and_ancestors.reorder(nil).select(:id) self_and_ancestors.reorder(nil).select(:id)
else else
id id
end end
GroupMember group_hierarchy_members = GroupMember.active_without_invites_and_requests
.active_without_invites_and_requests .where(source_id: source_ids)
.where(source_id: source_ids)
GroupMember.from_union([group_hierarchy_members,
members_from_self_and_ancestor_group_shares])
end end
def members_from_self_and_ancestors_with_effective_access_level def members_from_self_and_ancestors_with_effective_access_level
...@@ -398,7 +400,7 @@ class Group < Namespace ...@@ -398,7 +400,7 @@ class Group < Namespace
.first .first
&.access_level &.access_level
max_member_access || max_member_access_for_user_from_shared_groups(user) || GroupMember::NO_ACCESS max_member_access || GroupMember::NO_ACCESS
end end
def mattermost_team_params def mattermost_team_params
...@@ -524,27 +526,39 @@ class Group < Namespace ...@@ -524,27 +526,39 @@ class Group < Namespace
errors.add(:visibility_level, "#{visibility} is not allowed since there are sub-groups with higher visibility.") errors.add(:visibility_level, "#{visibility} is not allowed since there are sub-groups with higher visibility.")
end end
def max_member_access_for_user_from_shared_groups(user) def members_from_self_and_ancestor_group_shares
group_group_link_table = GroupGroupLink.arel_table group_group_link_table = GroupGroupLink.arel_table
group_member_table = GroupMember.arel_table group_member_table = GroupMember.arel_table
group_group_links_query = GroupGroupLink.where(shared_group_id: self_and_ancestors_ids) source_ids =
if has_parent?
self_and_ancestors.reorder(nil).select(:id)
else
id
end
group_group_links_query = GroupGroupLink.where(shared_group_id: source_ids)
cte = Gitlab::SQL::CTE.new(:group_group_links_cte, group_group_links_query) cte = Gitlab::SQL::CTE.new(:group_group_links_cte, group_group_links_query)
cte_alias = cte.table.alias(GroupGroupLink.table_name) cte_alias = cte.table.alias(GroupGroupLink.table_name)
link = GroupGroupLink # Instead of members.access_level, we need to maximize that access_level at
.with(cte.to_arel) # the respective group_group_links.group_access.
.select(smallest_value_arel([cte_alias[:group_access], group_member_table[:access_level]], member_columns = GroupMember.attribute_names.map do |column_name|
'group_access')) if column_name == 'access_level'
.from([group_member_table, cte.alias_to(group_group_link_table)]) smallest_value_arel([cte_alias[:group_access], group_member_table[:access_level]],
.where(group_member_table[:user_id].eq(user.id)) 'access_level')
.where(group_member_table[:requested_at].eq(nil)) else
.where(group_member_table[:source_id].eq(group_group_link_table[:shared_with_group_id])) group_member_table[column_name]
.where(group_member_table[:source_type].eq('Namespace')) end
.reorder(Arel::Nodes::Descending.new(group_group_link_table[:group_access])) end
.first
GroupMember
link&.group_access .with(cte.to_arel)
.select(*member_columns)
.from([group_member_table, cte.alias_to(group_group_link_table)])
.where(group_member_table[:requested_at].eq(nil))
.where(group_member_table[:source_id].eq(group_group_link_table[:shared_with_group_id]))
.where(group_member_table[:source_type].eq('Namespace'))
end end
def smallest_value_arel(args, column_alias) def smallest_value_arel(args, column_alias)
......
...@@ -88,6 +88,7 @@ module Groups ...@@ -88,6 +88,7 @@ module Groups
end end
@group.parent = @new_parent_group @group.parent = @new_parent_group
@group.clear_memoization(:self_and_ancestors_ids)
@group.save! @group.save!
end end
......
---
title: Group authorization refresh to consider shared groups
merge_request: 31204
author:
type: fixed
...@@ -662,6 +662,19 @@ describe Group do ...@@ -662,6 +662,19 @@ describe Group do
expect(group.members_with_parents).to include(developer) expect(group.members_with_parents).to include(developer)
expect(group.members_with_parents).to include(maintainer) expect(group.members_with_parents).to include(maintainer)
end end
context 'group sharing' do
let!(:shared_group) { create(:group) }
before do
create(:group_group_link, shared_group: shared_group, shared_with_group: group)
end
it 'returns shared with group members' do
expect(shared_group.members_with_parents).to(
include(developer))
end
end
end end
describe '#members_from_self_and_ancestors_with_effective_access_level' do describe '#members_from_self_and_ancestors_with_effective_access_level' do
...@@ -800,6 +813,22 @@ describe Group do ...@@ -800,6 +813,22 @@ describe Group do
expect(group.user_ids_for_project_authorizations) expect(group.user_ids_for_project_authorizations)
.to include(maintainer.id, developer.id) .to include(maintainer.id, developer.id)
end end
context 'group sharing' do
let_it_be(:group) { create(:group) }
let_it_be(:group_user) { create(:user) }
let_it_be(:shared_group) { create(:group) }
before do
group.add_developer(group_user)
create(:group_group_link, shared_group: shared_group, shared_with_group: group)
end
it 'returns the user IDs for shared with group members' do
expect(shared_group.user_ids_for_project_authorizations).to(
include(group_user.id))
end
end
end end
describe '#update_two_factor_requirement' do describe '#update_two_factor_requirement' do
......
...@@ -56,21 +56,47 @@ describe AuthorizedProjectUpdate::ProjectCreateService do ...@@ -56,21 +56,47 @@ describe AuthorizedProjectUpdate::ProjectCreateService do
end end
context 'membership overrides' do context 'membership overrides' do
before do context 'group hierarchy' do
create(:group_member, access_level: Gitlab::Access::REPORTER, group: group_parent, user: group_user) before do
create(:group_member, access_level: Gitlab::Access::DEVELOPER, group: group, user: group_user) create(:group_member, access_level: Gitlab::Access::REPORTER, group: group_parent, user: group_user)
ProjectAuthorization.delete_all create(:group_member, access_level: Gitlab::Access::DEVELOPER, group: group, user: group_user)
ProjectAuthorization.delete_all
end
it 'creates project authorization' do
expect { service.execute }.to(
change { ProjectAuthorization.count }.from(0).to(1))
project_authorization = ProjectAuthorization.where(
project_id: group_project.id,
user_id: group_user.id,
access_level: Gitlab::Access::DEVELOPER)
expect(project_authorization).to exist
end
end end
it 'creates project authorization' do context 'group sharing' do
expect { service.execute }.to( let!(:shared_with_group) { create(:group) }
change { ProjectAuthorization.count }.from(0).to(1))
project_authorization = ProjectAuthorization.where( before do
project_id: group_project.id, create(:group_member, access_level: Gitlab::Access::REPORTER, group: group, user: group_user)
user_id: group_user.id, create(:group_member, access_level: Gitlab::Access::MAINTAINER, group: shared_with_group, user: group_user)
access_level: Gitlab::Access::DEVELOPER)
expect(project_authorization).to exist create(:group_group_link, shared_group: group, shared_with_group: shared_with_group, group_access: Gitlab::Access::DEVELOPER)
ProjectAuthorization.delete_all
end
it 'creates project authorization' do
expect { service.execute }.to(
change { ProjectAuthorization.count }.from(0).to(1))
project_authorization = ProjectAuthorization.where(
project_id: group_project.id,
user_id: group_user.id,
access_level: Gitlab::Access::DEVELOPER)
expect(project_authorization).to exist
end
end end
end end
......
...@@ -88,6 +88,116 @@ describe Projects::CreateService, '#execute' do ...@@ -88,6 +88,116 @@ describe Projects::CreateService, '#execute' do
end end
end end
context 'group sharing', :sidekiq_inline do
let_it_be(:group) { create(:group) }
let_it_be(:shared_group) { create(:group) }
let_it_be(:shared_group_user) { create(:user) }
let(:opts) do
{
name: 'GitLab',
namespace_id: shared_group.id
}
end
before do
create(:group_group_link, shared_group: shared_group, shared_with_group: group)
shared_group.add_maintainer(shared_group_user)
group.add_developer(user)
end
it 'updates authorization' do
shared_group_project = create_project(shared_group_user, opts)
expect(
Ability.allowed?(shared_group_user, :read_project, shared_group_project)
).to be_truthy
expect(
Ability.allowed?(user, :read_project, shared_group_project)
).to be_truthy
end
end
context 'membership overrides', :sidekiq_inline do
let_it_be(:group) { create(:group, :private) }
let_it_be(:subgroup_for_projects) { create(:group, :private, parent: group) }
let_it_be(:subgroup_for_access) { create(:group, :private, parent: group) }
let_it_be(:group_maintainer) { create(:user) }
let(:group_access_level) { Gitlab::Access::REPORTER }
let(:subgroup_access_level) { Gitlab::Access::DEVELOPER }
let(:share_max_access_level) { Gitlab::Access::MAINTAINER }
let(:opts) do
{
name: 'GitLab',
namespace_id: subgroup_for_projects.id
}
end
before do
group.add_maintainer(group_maintainer)
create(:group_group_link, shared_group: subgroup_for_projects,
shared_with_group: subgroup_for_access,
group_access: share_max_access_level)
end
context 'membership is higher from group hierarchy' do
let(:group_access_level) { Gitlab::Access::MAINTAINER }
it 'updates authorization' do
create(:group_member, access_level: subgroup_access_level, group: subgroup_for_access, user: user)
create(:group_member, access_level: group_access_level, group: group, user: user)
subgroup_project = create_project(group_maintainer, opts)
project_authorization = ProjectAuthorization.where(
project_id: subgroup_project.id,
user_id: user.id,
access_level: group_access_level)
expect(project_authorization).to exist
end
end
context 'membership is higher from group share' do
let(:subgroup_access_level) { Gitlab::Access::MAINTAINER }
context 'share max access level is not limiting' do
it 'updates authorization' do
create(:group_member, access_level: group_access_level, group: group, user: user)
create(:group_member, access_level: subgroup_access_level, group: subgroup_for_access, user: user)
subgroup_project = create_project(group_maintainer, opts)
project_authorization = ProjectAuthorization.where(
project_id: subgroup_project.id,
user_id: user.id,
access_level: subgroup_access_level)
expect(project_authorization).to exist
end
end
context 'share max access level is limiting' do
let(:share_max_access_level) { Gitlab::Access::DEVELOPER }
it 'updates authorization' do
create(:group_member, access_level: group_access_level, group: group, user: user)
create(:group_member, access_level: subgroup_access_level, group: subgroup_for_access, user: user)
subgroup_project = create_project(group_maintainer, opts)
project_authorization = ProjectAuthorization.where(
project_id: subgroup_project.id,
user_id: user.id,
access_level: share_max_access_level)
expect(project_authorization).to exist
end
end
end
end
context 'error handling' do context 'error handling' do
it 'handles invalid options' do it 'handles invalid options' do
opts[:default_branch] = 'master' opts[:default_branch] = 'master'
......
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