Commit f80dd77c authored by Yorick Peterse's avatar Yorick Peterse

Merge branch 'if-share_groups_with_groups_be' into 'master'

Share groups with groups (BE)

See merge request gitlab-org/gitlab!17117
parents d455f3ba b86a9b14
# frozen_string_literal: true
class Groups::GroupLinksController < Groups::ApplicationController
before_action :check_feature_flag!
before_action :authorize_admin_group!
def create
shared_with_group = Group.find(params[:shared_with_group_id]) if params[:shared_with_group_id].present?
if shared_with_group
result = Groups::GroupLinks::CreateService
.new(shared_with_group, current_user, group_link_create_params)
.execute(group)
return render_404 if result[:http_status] == 404
flash[:alert] = result[:message] if result[:status] == :error
else
flash[:alert] = _('Please select a group.')
end
redirect_to group_group_members_path(group)
end
private
def group_link_create_params
params.permit(:shared_group_access, :expires_at)
end
def check_feature_flag!
render_404 unless Feature.enabled?(:share_group_with_group)
end
end
...@@ -30,6 +30,10 @@ class Group < Namespace ...@@ -30,6 +30,10 @@ class Group < Namespace
has_many :members_and_requesters, as: :source, class_name: 'GroupMember' has_many :members_and_requesters, as: :source, class_name: 'GroupMember'
has_many :milestones has_many :milestones
has_many :shared_group_links, foreign_key: :shared_with_group_id, class_name: 'GroupGroupLink'
has_many :shared_with_group_links, foreign_key: :shared_group_id, class_name: 'GroupGroupLink'
has_many :shared_groups, through: :shared_group_links, source: :shared_group
has_many :shared_with_groups, through: :shared_with_group_links, source: :shared_with_group
has_many :project_group_links, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent has_many :project_group_links, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_many :shared_projects, through: :project_group_links, source: :project has_many :shared_projects, through: :project_group_links, source: :project
...@@ -376,11 +380,12 @@ class Group < Namespace ...@@ -376,11 +380,12 @@ class Group < Namespace
return GroupMember::OWNER if user.admin? return GroupMember::OWNER if user.admin?
members_with_parents max_member_access = members_with_parents.where(user_id: user)
.where(user_id: user)
.reorder(access_level: :desc) .reorder(access_level: :desc)
.first&. .first
access_level || GroupMember::NO_ACCESS &.access_level
max_member_access || max_member_access_for_user_from_shared_groups(user) || GroupMember::NO_ACCESS
end end
def mattermost_team_params def mattermost_team_params
...@@ -474,6 +479,26 @@ class Group < Namespace ...@@ -474,6 +479,26 @@ 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)
return unless Feature.enabled?(:share_group_with_group)
group_group_link_table = GroupGroupLink.arel_table
group_member_table = GroupMember.arel_table
group_group_links_query = GroupGroupLink.where(shared_group_id: self_and_ancestors_ids)
cte = Gitlab::SQL::CTE.new(:group_group_links_cte, group_group_links_query)
link = GroupGroupLink
.with(cte.to_arel)
.from([group_member_table, cte.alias_to(group_group_link_table)])
.where(group_member_table[:user_id].eq(user.id))
.where(group_member_table[:source_id].eq(group_group_link_table[:shared_with_group_id]))
.reorder(Arel::Nodes::Descending.new(group_group_link_table[:group_access]))
.first
link&.group_access
end
def self.groups_including_descendants_by(group_ids) def self.groups_including_descendants_by(group_ids)
Gitlab::ObjectHierarchy Gitlab::ObjectHierarchy
.new(Group.where(id: group_ids)) .new(Group.where(id: group_ids))
......
# frozen_string_literal: true
class GroupGroupLink < ApplicationRecord
include Expirable
belongs_to :shared_group, class_name: 'Group', foreign_key: :shared_group_id
belongs_to :shared_with_group, class_name: 'Group', foreign_key: :shared_with_group_id
validates :shared_group, presence: true
validates :shared_group_id, uniqueness: { scope: [:shared_with_group_id],
message: _('The group has already been shared with this group') }
validates :shared_with_group, presence: true
validates :group_access, inclusion: { in: Gitlab::Access.values },
presence: true
def self.access_options
Gitlab::Access.options
end
def self.default_access
Gitlab::Access::DEVELOPER
end
end
# frozen_string_literal: true
module Groups
module GroupLinks
class CreateService < BaseService
def execute(shared_group)
unless group && shared_group &&
can?(current_user, :admin_group, shared_group) &&
can?(current_user, :read_group, group)
return error('Not Found', 404)
end
link = GroupGroupLink.new(
shared_group: shared_group,
shared_with_group: group,
group_access: params[:shared_group_access],
expires_at: params[:expires_at]
)
if link.save
group.refresh_members_authorized_projects
success(link: link)
else
error(link.errors.full_messages.to_sentence, 409)
end
end
end
end
end
---
title: Share groups with groups
merge_request: 17117
author:
type: added
...@@ -62,6 +62,8 @@ constraints(::Constraints::GroupUrlConstrainer.new) do ...@@ -62,6 +62,8 @@ constraints(::Constraints::GroupUrlConstrainer.new) do
delete :leave, on: :collection delete :leave, on: :collection
end end
resources :group_links, only: [:index, :create, :update, :destroy], constraints: { id: /\d+/ }
resources :uploads, only: [:create] do resources :uploads, only: [:create] do
collection do collection do
get ":secret/:filename", action: :show, as: :show, constraints: { filename: %r{[^/]+} } get ":secret/:filename", action: :show, as: :show, constraints: { filename: %r{[^/]+} }
......
# frozen_string_literal: true
class CreateGroupGroupLinks < ActiveRecord::Migration[5.2]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
def up
create_table :group_group_links do |t|
t.timestamps_with_timezone null: false
t.references :shared_group, null: false,
index: false,
foreign_key: { on_delete: :cascade,
to_table: :namespaces }
t.references :shared_with_group, null: false,
foreign_key: { on_delete: :cascade,
to_table: :namespaces }
t.date :expires_at
t.index [:shared_group_id, :shared_with_group_id],
{ unique: true,
name: 'index_group_group_links_on_shared_group_and_shared_with_group' }
t.integer :group_access, { limit: 2,
default: 30, # Gitlab::Access::DEVELOPER
null: false }
end
end
def down
drop_table :group_group_links
end
end
...@@ -1820,6 +1820,17 @@ ActiveRecord::Schema.define(version: 2019_10_26_124116) do ...@@ -1820,6 +1820,17 @@ ActiveRecord::Schema.define(version: 2019_10_26_124116) do
t.index ["key", "value"], name: "index_group_custom_attributes_on_key_and_value" t.index ["key", "value"], name: "index_group_custom_attributes_on_key_and_value"
end end
create_table "group_group_links", force: :cascade do |t|
t.datetime_with_timezone "created_at", null: false
t.datetime_with_timezone "updated_at", null: false
t.bigint "shared_group_id", null: false
t.bigint "shared_with_group_id", null: false
t.date "expires_at"
t.integer "group_access", limit: 2, default: 30, null: false
t.index ["shared_group_id", "shared_with_group_id"], name: "index_group_group_links_on_shared_group_and_shared_with_group", unique: true
t.index ["shared_with_group_id"], name: "index_group_group_links_on_shared_with_group_id"
end
create_table "historical_data", id: :serial, force: :cascade do |t| create_table "historical_data", id: :serial, force: :cascade do |t|
t.date "date", null: false t.date "date", null: false
t.integer "active_user_count" t.integer "active_user_count"
...@@ -4220,6 +4231,8 @@ ActiveRecord::Schema.define(version: 2019_10_26_124116) do ...@@ -4220,6 +4231,8 @@ ActiveRecord::Schema.define(version: 2019_10_26_124116) do
add_foreign_key "gpg_signatures", "projects", on_delete: :cascade add_foreign_key "gpg_signatures", "projects", on_delete: :cascade
add_foreign_key "grafana_integrations", "projects", on_delete: :cascade add_foreign_key "grafana_integrations", "projects", on_delete: :cascade
add_foreign_key "group_custom_attributes", "namespaces", column: "group_id", on_delete: :cascade add_foreign_key "group_custom_attributes", "namespaces", column: "group_id", on_delete: :cascade
add_foreign_key "group_group_links", "namespaces", column: "shared_group_id", on_delete: :cascade
add_foreign_key "group_group_links", "namespaces", column: "shared_with_group_id", on_delete: :cascade
add_foreign_key "identities", "saml_providers", name: "fk_aade90f0fc", on_delete: :cascade add_foreign_key "identities", "saml_providers", name: "fk_aade90f0fc", on_delete: :cascade
add_foreign_key "import_export_uploads", "projects", on_delete: :cascade add_foreign_key "import_export_uploads", "projects", on_delete: :cascade
add_foreign_key "index_statuses", "projects", name: "fk_74b2492545", on_delete: :cascade add_foreign_key "index_statuses", "projects", name: "fk_74b2492545", on_delete: :cascade
......
...@@ -57,7 +57,7 @@ module Gitlab ...@@ -57,7 +57,7 @@ module Gitlab
private private
# Builds a recursive CTE that gets all the groups the current user has # Builds a recursive CTE that gets all the groups the current user has
# access to, including any nested groups. # access to, including any nested groups and any shared groups.
def recursive_cte def recursive_cte
cte = Gitlab::SQL::RecursiveCTE.new(:namespaces_cte) cte = Gitlab::SQL::RecursiveCTE.new(:namespaces_cte)
members = Member.arel_table members = Member.arel_table
...@@ -68,20 +68,27 @@ module Gitlab ...@@ -68,20 +68,27 @@ module Gitlab
.select([namespaces[:id], members[:access_level]]) .select([namespaces[:id], members[:access_level]])
.except(:order) .except(:order)
if Feature.enabled?(:share_group_with_group)
# Namespaces shared with any of the group
cte << Group.select([namespaces[:id], 'group_group_links.group_access AS access_level'])
.joins(join_group_group_links)
.joins(join_members_on_group_group_links)
end
# Sub groups of any groups the user is a member of. # Sub groups of any groups the user is a member of.
cte << Group.select([ cte << Group.select([
namespaces[:id], namespaces[:id],
greatest(members[:access_level], cte.table[:access_level], 'access_level') greatest(members[:access_level], cte.table[:access_level], 'access_level')
]) ])
.joins(join_cte(cte)) .joins(join_cte(cte))
.joins(join_members) .joins(join_members_on_namespaces)
.except(:order) .except(:order)
cte cte
end end
# Builds a LEFT JOIN to join optional memberships onto the CTE. # Builds a LEFT JOIN to join optional memberships onto the CTE.
def join_members def join_members_on_namespaces
members = Member.arel_table members = Member.arel_table
namespaces = Namespace.arel_table namespaces = Namespace.arel_table
...@@ -94,6 +101,23 @@ module Gitlab ...@@ -94,6 +101,23 @@ module Gitlab
Arel::Nodes::OuterJoin.new(members, Arel::Nodes::On.new(cond)) Arel::Nodes::OuterJoin.new(members, Arel::Nodes::On.new(cond))
end end
def join_group_group_links
group_group_links = GroupGroupLink.arel_table
namespaces = Namespace.arel_table
cond = group_group_links[:shared_group_id].eq(namespaces[:id])
Arel::Nodes::InnerJoin.new(group_group_links, Arel::Nodes::On.new(cond))
end
def join_members_on_group_group_links
group_group_links = GroupGroupLink.arel_table
members = Member.arel_table
cond = group_group_links[:shared_with_group_id].eq(members[:source_id])
.and(members[:user_id].eq(user.id))
Arel::Nodes::InnerJoin.new(members, Arel::Nodes::On.new(cond))
end
# Builds an INNER JOIN to join namespaces onto the CTE. # Builds an INNER JOIN to join namespaces onto the CTE.
def join_cte(cte) def join_cte(cte)
namespaces = Namespace.arel_table namespaces = Namespace.arel_table
......
...@@ -16437,6 +16437,9 @@ msgstr "" ...@@ -16437,6 +16437,9 @@ msgstr ""
msgid "The group and its projects can only be viewed by members." msgid "The group and its projects can only be viewed by members."
msgstr "" msgstr ""
msgid "The group has already been shared with this group"
msgstr ""
msgid "The group settings for %{group_links} require you to enable Two-Factor Authentication for your account. You can %{leave_group_links}." msgid "The group settings for %{group_links} require you to enable Two-Factor Authentication for your account. You can %{leave_group_links}."
msgstr "" msgstr ""
......
# frozen_string_literal: true
require 'spec_helper'
describe Groups::GroupLinksController do
let(:shared_with_group) { create(:group, :private) }
let(:shared_group) { create(:group, :private) }
let(:user) { create(:user) }
before do
sign_in(user)
end
describe '#create' do
let(:shared_with_group_id) { shared_with_group.id }
subject do
post(:create,
params: { group_id: shared_group,
shared_with_group_id: shared_with_group_id,
shared_group_access: GroupGroupLink.default_access })
end
context 'when user has correct access to both groups' do
let(:group_member) { create(:user) }
before do
shared_with_group.add_developer(user)
shared_group.add_owner(user)
shared_with_group.add_developer(group_member)
end
it 'links group with selected group' do
expect { subject }.to change { shared_with_group.shared_groups.include?(shared_group) }.from(false).to(true)
end
it 'redirects to group links page' do
subject
expect(response).to(redirect_to(group_group_members_path(shared_group)))
end
it 'allows access for group member' do
expect { subject }.to change { group_member.can?(:read_group, shared_group) }.from(false).to(true)
end
context 'when shared with group id is not present' do
let(:shared_with_group_id) { nil }
it 'redirects to group links page' do
subject
expect(response).to(redirect_to(group_group_members_path(shared_group)))
expect(flash[:alert]).to eq('Please select a group.')
end
end
context 'when link is not persisted in the database' do
before do
allow(::Groups::GroupLinks::CreateService).to(
receive_message_chain(:new, :execute)
.and_return({ status: :error,
http_status: 409,
message: 'error' }))
end
it 'redirects to group links page' do
subject
expect(response).to(redirect_to(group_group_members_path(shared_group)))
expect(flash[:alert]).to eq('error')
end
end
context 'when feature flag is disabled' do
before do
stub_feature_flags(share_group_with_group: false)
end
it 'renders 404' do
subject
expect(response).to have_gitlab_http_status(404)
end
end
end
context 'when user does not have access to the group' do
before do
shared_group.add_owner(user)
end
it 'renders 404' do
subject
expect(response).to have_gitlab_http_status(404)
end
end
context 'when user does not have admin access to the shared group' do
before do
shared_with_group.add_developer(user)
shared_group.add_developer(user)
end
it 'renders 404' do
subject
expect(response).to have_gitlab_http_status(404)
end
end
end
end
# frozen_string_literal: true
FactoryBot.define do
factory :group_group_link do
shared_group { create(:group) }
shared_with_group { create(:group) }
group_access { GroupMember::DEVELOPER }
end
end
...@@ -3,28 +3,28 @@ ...@@ -3,28 +3,28 @@
require 'spec_helper' require 'spec_helper'
describe Gitlab::ProjectAuthorizations do describe Gitlab::ProjectAuthorizations do
let(:group) { create(:group) }
let!(:owned_project) { create(:project) }
let!(:other_project) { create(:project) }
let!(:group_project) { create(:project, namespace: group) }
let(:user) { owned_project.namespace.owner }
def map_access_levels(rows) def map_access_levels(rows)
rows.each_with_object({}) do |row, hash| rows.each_with_object({}) do |row, hash|
hash[row.project_id] = row.access_level hash[row.project_id] = row.access_level
end end
end end
subject(:authorizations) do
described_class.new(user).calculate
end
context 'user added to group and project' do
let(:group) { create(:group) }
let!(:other_project) { create(:project) }
let!(:group_project) { create(:project, namespace: group) }
let!(:owned_project) { create(:project) }
let(:user) { owned_project.namespace.owner }
before do before do
other_project.add_reporter(user) other_project.add_reporter(user)
group.add_developer(user) group.add_developer(user)
end end
let(:authorizations) do
described_class.new(user).calculate
end
it 'returns the correct number of authorizations' do it 'returns the correct number of authorizations' do
expect(authorizations.length).to eq(3) expect(authorizations.length).to eq(3)
end end
...@@ -41,10 +41,17 @@ describe Gitlab::ProjectAuthorizations do ...@@ -41,10 +41,17 @@ describe Gitlab::ProjectAuthorizations do
expect(mapping[other_project.id]).to eq(Gitlab::Access::REPORTER) expect(mapping[other_project.id]).to eq(Gitlab::Access::REPORTER)
expect(mapping[group_project.id]).to eq(Gitlab::Access::DEVELOPER) expect(mapping[group_project.id]).to eq(Gitlab::Access::DEVELOPER)
end end
end
context 'with nested groups' do context 'with nested groups' do
let(:group) { create(:group) }
let!(:nested_group) { create(:group, parent: group) } let!(:nested_group) { create(:group, parent: group) }
let!(:nested_project) { create(:project, namespace: nested_group) } let!(:nested_project) { create(:project, namespace: nested_group) }
let(:user) { create(:user) }
before do
group.add_developer(user)
end
it 'includes nested groups' do it 'includes nested groups' do
expect(authorizations.pluck(:project_id)).to include(nested_project.id) expect(authorizations.pluck(:project_id)).to include(nested_project.id)
...@@ -64,4 +71,114 @@ describe Gitlab::ProjectAuthorizations do ...@@ -64,4 +71,114 @@ describe Gitlab::ProjectAuthorizations do
expect(mapping[nested_project.id]).to eq(Gitlab::Access::MAINTAINER) expect(mapping[nested_project.id]).to eq(Gitlab::Access::MAINTAINER)
end end
end end
context 'with shared groups' do
let(:parent_group_user) { create(:user) }
let(:group_user) { create(:user) }
let(:child_group_user) { create(:user) }
set(:group_parent) { create(:group, :private) }
set(:group) { create(:group, :private, parent: group_parent) }
set(:group_child) { create(:group, :private, parent: group) }
set(:shared_group_parent) { create(:group, :private) }
set(:shared_group) { create(:group, :private, parent: shared_group_parent) }
set(:shared_group_child) { create(:group, :private, parent: shared_group) }
set(:project_parent) { create(:project, group: shared_group_parent) }
set(:project) { create(:project, group: shared_group) }
set(:project_child) { create(:project, group: shared_group_child) }
before do
group_parent.add_owner(parent_group_user)
group.add_owner(group_user)
group_child.add_owner(child_group_user)
create(:group_group_link, shared_group: shared_group, shared_with_group: group)
end
context 'when feature flag share_group_with_group is enabled' do
before do
stub_feature_flags(share_group_with_group: true)
end
context 'group user' do
let(:user) { group_user }
it 'creates proper authorizations' do
mapping = map_access_levels(authorizations)
expect(mapping[project_parent.id]).to be_nil
expect(mapping[project.id]).to eq(Gitlab::Access::DEVELOPER)
expect(mapping[project_child.id]).to eq(Gitlab::Access::DEVELOPER)
end
end
context 'parent group user' do
let(:user) { parent_group_user }
it 'creates proper authorizations' do
mapping = map_access_levels(authorizations)
expect(mapping[project_parent.id]).to be_nil
expect(mapping[project.id]).to be_nil
expect(mapping[project_child.id]).to be_nil
end
end
context 'child group user' do
let(:user) { child_group_user }
it 'creates proper authorizations' do
mapping = map_access_levels(authorizations)
expect(mapping[project_parent.id]).to be_nil
expect(mapping[project.id]).to be_nil
expect(mapping[project_child.id]).to be_nil
end
end
end
context 'when feature flag share_group_with_group is disabled' do
before do
stub_feature_flags(share_group_with_group: false)
end
context 'group user' do
let(:user) { group_user }
it 'creates proper authorizations' do
mapping = map_access_levels(authorizations)
expect(mapping[project_parent.id]).to be_nil
expect(mapping[project.id]).to be_nil
expect(mapping[project_child.id]).to be_nil
end
end
context 'parent group user' do
let(:user) { parent_group_user }
it 'creates proper authorizations' do
mapping = map_access_levels(authorizations)
expect(mapping[project_parent.id]).to be_nil
expect(mapping[project.id]).to be_nil
expect(mapping[project_child.id]).to be_nil
end
end
context 'child group user' do
let(:user) { child_group_user }
it 'creates proper authorizations' do
mapping = map_access_levels(authorizations)
expect(mapping[project_parent.id]).to be_nil
expect(mapping[project.id]).to be_nil
expect(mapping[project_child.id]).to be_nil
end
end
end
end
end end
# frozen_string_literal: true
require 'spec_helper'
describe GroupGroupLink do
let_it_be(:group) { create(:group) }
let_it_be(:shared_group) { create(:group) }
let_it_be(:group_group_link) do
create(:group_group_link, shared_group: shared_group,
shared_with_group: group)
end
describe 'relations' do
it { is_expected.to belong_to(:shared_group) }
it { is_expected.to belong_to(:shared_with_group) }
end
describe 'validation' do
it { is_expected.to validate_presence_of(:shared_group) }
it do
is_expected.to(
validate_uniqueness_of(:shared_group_id)
.scoped_to(:shared_with_group_id)
.with_message('The group has already been shared with this group'))
end
it { is_expected.to validate_presence_of(:shared_with_group) }
it { is_expected.to validate_presence_of(:group_access) }
it do
is_expected.to(
validate_inclusion_of(:group_access).in_array(Gitlab::Access.values))
end
end
end
...@@ -525,6 +525,128 @@ describe Group do ...@@ -525,6 +525,128 @@ describe Group do
it { expect(subject.parent).to be_kind_of(described_class) } it { expect(subject.parent).to be_kind_of(described_class) }
end end
describe '#max_member_access_for_user' do
context 'group shared with another group' do
let(:parent_group_user) { create(:user) }
let(:group_user) { create(:user) }
let(:child_group_user) { create(:user) }
set(:group_parent) { create(:group, :private) }
set(:group) { create(:group, :private, parent: group_parent) }
set(:group_child) { create(:group, :private, parent: group) }
set(:shared_group_parent) { create(:group, :private) }
set(:shared_group) { create(:group, :private, parent: shared_group_parent) }
set(:shared_group_child) { create(:group, :private, parent: shared_group) }
before do
group_parent.add_owner(parent_group_user)
group.add_owner(group_user)
group_child.add_owner(child_group_user)
create(:group_group_link, { shared_with_group: group,
shared_group: shared_group,
group_access: GroupMember::DEVELOPER })
end
context 'when feature flag share_group_with_group is enabled' do
before do
stub_feature_flags(share_group_with_group: true)
end
context 'with user in the group' do
let(:user) { group_user }
it 'returns correct access level' do
expect(shared_group_parent.max_member_access_for_user(user)).to eq(Gitlab::Access::NO_ACCESS)
expect(shared_group.max_member_access_for_user(user)).to eq(Gitlab::Access::DEVELOPER)
expect(shared_group_child.max_member_access_for_user(user)).to eq(Gitlab::Access::DEVELOPER)
end
end
context 'with user in the parent group' do
let(:user) { parent_group_user }
it 'returns correct access level' do
expect(shared_group_parent.max_member_access_for_user(user)).to eq(Gitlab::Access::NO_ACCESS)
expect(shared_group.max_member_access_for_user(user)).to eq(Gitlab::Access::NO_ACCESS)
expect(shared_group_child.max_member_access_for_user(user)).to eq(Gitlab::Access::NO_ACCESS)
end
end
context 'with user in the child group' do
let(:user) { child_group_user }
it 'returns correct access level' do
expect(shared_group_parent.max_member_access_for_user(user)).to eq(Gitlab::Access::NO_ACCESS)
expect(shared_group.max_member_access_for_user(user)).to eq(Gitlab::Access::NO_ACCESS)
expect(shared_group_child.max_member_access_for_user(user)).to eq(Gitlab::Access::NO_ACCESS)
end
end
end
context 'when feature flag share_group_with_group is disabled' do
before do
stub_feature_flags(share_group_with_group: false)
end
context 'with user in the group' do
let(:user) { group_user }
it 'returns correct access level' do
expect(shared_group_parent.max_member_access_for_user(user)).to eq(Gitlab::Access::NO_ACCESS)
expect(shared_group.max_member_access_for_user(user)).to eq(Gitlab::Access::NO_ACCESS)
expect(shared_group_child.max_member_access_for_user(user)).to eq(Gitlab::Access::NO_ACCESS)
end
end
context 'with user in the parent group' do
let(:user) { parent_group_user }
it 'returns correct access level' do
expect(shared_group_parent.max_member_access_for_user(user)).to eq(Gitlab::Access::NO_ACCESS)
expect(shared_group.max_member_access_for_user(user)).to eq(Gitlab::Access::NO_ACCESS)
expect(shared_group_child.max_member_access_for_user(user)).to eq(Gitlab::Access::NO_ACCESS)
end
end
context 'with user in the child group' do
let(:user) { child_group_user }
it 'returns correct access level' do
expect(shared_group_parent.max_member_access_for_user(user)).to eq(Gitlab::Access::NO_ACCESS)
expect(shared_group.max_member_access_for_user(user)).to eq(Gitlab::Access::NO_ACCESS)
expect(shared_group_child.max_member_access_for_user(user)).to eq(Gitlab::Access::NO_ACCESS)
end
end
end
end
context 'multiple groups shared with group' do
let(:user) { create(:user) }
let(:group) { create(:group, :private) }
let(:shared_group_parent) { create(:group, :private) }
let(:shared_group) { create(:group, :private, parent: shared_group_parent) }
before do
stub_feature_flags(share_group_with_group: true)
group.add_owner(user)
create(:group_group_link, { shared_with_group: group,
shared_group: shared_group,
group_access: GroupMember::DEVELOPER })
create(:group_group_link, { shared_with_group: group,
shared_group: shared_group_parent,
group_access: GroupMember::MAINTAINER })
end
it 'returns correct access level' do
expect(shared_group.max_member_access_for_user(user)).to eq(Gitlab::Access::MAINTAINER)
end
end
end
describe '#members_with_parents' do describe '#members_with_parents' do
let!(:group) { create(:group, :nested) } let!(:group) { create(:group, :nested) }
let!(:maintainer) { group.parent.add_user(create(:user), GroupMember::MAINTAINER) } let!(:maintainer) { group.parent.add_user(create(:user), GroupMember::MAINTAINER) }
......
# frozen_string_literal: true
require 'spec_helper'
describe Groups::GroupLinks::CreateService, '#execute' do
let(:parent_group_user) { create(:user) }
let(:group_user) { create(:user) }
let(:child_group_user) { create(:user) }
set(:group_parent) { create(:group, :private) }
set(:group) { create(:group, :private, parent: group_parent) }
set(:group_child) { create(:group, :private, parent: group) }
set(:shared_group_parent) { create(:group, :private) }
set(:shared_group) { create(:group, :private, parent: shared_group_parent) }
set(:shared_group_child) { create(:group, :private, parent: shared_group) }
set(:project_parent) { create(:project, group: shared_group_parent) }
set(:project) { create(:project, group: shared_group) }
set(:project_child) { create(:project, group: shared_group_child) }
let(:opts) do
{
shared_group_access: Gitlab::Access::DEVELOPER,
expires_at: nil
}
end
let(:user) { group_user }
subject { described_class.new(group, user, opts) }
before do
group.add_guest(group_user)
shared_group.add_owner(group_user)
end
it 'adds group to another group' do
expect { subject.execute(shared_group) }.to change { group.shared_group_links.count }.from(0).to(1)
end
it 'returns false if shared group is blank' do
expect { subject.execute(nil) }.not_to change { group.shared_group_links.count }
end
context 'user does not have access to group' do
let(:user) { create(:user) }
before do
shared_group.add_owner(user)
end
it 'returns error' do
result = subject.execute(shared_group)
expect(result[:status]).to eq(:error)
expect(result[:http_status]).to eq(404)
end
end
context 'user does not have admin access to shared group' do
let(:user) { create(:user) }
before do
group.add_guest(user)
shared_group.add_developer(user)
end
it 'returns error' do
result = subject.execute(shared_group)
expect(result[:status]).to eq(:error)
expect(result[:http_status]).to eq(404)
end
end
context 'group hierarchies' do
before do
group_parent.add_owner(parent_group_user)
group.add_owner(group_user)
group_child.add_owner(child_group_user)
end
context 'group user' do
let(:user) { group_user }
it 'create proper authorizations' do
subject.execute(shared_group)
expect(Ability.allowed?(user, :read_project, project_parent)).to be_falsey
expect(Ability.allowed?(user, :read_project, project)).to be_truthy
expect(Ability.allowed?(user, :read_project, project_child)).to be_truthy
end
end
context 'parent group user' do
let(:user) { parent_group_user }
it 'create proper authorizations' do
subject.execute(shared_group)
expect(Ability.allowed?(user, :read_project, project_parent)).to be_falsey
expect(Ability.allowed?(user, :read_project, project)).to be_falsey
expect(Ability.allowed?(user, :read_project, project_child)).to be_falsey
end
end
context 'child group user' do
let(:user) { child_group_user }
it 'create proper authorizations' do
subject.execute(shared_group)
expect(Ability.allowed?(user, :read_project, project_parent)).to be_falsey
expect(Ability.allowed?(user, :read_project, project)).to be_falsey
expect(Ability.allowed?(user, :read_project, project_child)).to be_falsey
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