Unverified Commit 6b6b928c authored by Imre Farkas's avatar Imre Farkas
Browse files

Share groups with groups

parent 623ef392
class Groups::GroupLinksController < Groups::ApplicationController
# before_action :authorize_admin_project!
# before_action :authorize_admin_project_member!, only: [:update]
before_action :group
def create
shared_group = Group.find(params[:shared_group_id]) if params[:shared_group_id].present?
Groups::GroupLinks::CreateService.new(group, current_user, group_link_create_params).execute(shared_group)
redirect_to group_group_members_path(group)
end
protected
def group_link_create_params
params.permit(:shared_group_access, :expires_at)
end
end
......@@ -27,6 +27,8 @@ class Group < Namespace
has_many :members_and_requesters, as: :source, class_name: 'GroupMember'
has_many :milestones
has_many :group_group_links, foreign_key: :shared_with_group_id
has_many :shared_groups, through: :group_group_links, source: :shared_group
has_many :project_group_links, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_many :shared_projects, through: :project_group_links, source: :project
......
# frozen_string_literal: true
class GroupGroupLink < ActiveRecord::Base
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: 'already shared with this group' }
validates :shared_with_group, presence: true
validates :group_access, 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)
return false unless shared_group
group.group_group_links.create(
shared_group: shared_group,
shared_with_group: group,
group_access: params[:shared_group_access],
expires_at: params[:expires_at]
)
end
end
end
end
.row
.col-sm-12
= form_tag group_group_links_path(@group), class: 'js-requires-input', method: :post do
.form-group
= label_tag :shared_group_id, 'Select a group to share with', class: 'label-bold'
-#= groups_select_tag(:link_group_id, data: { skip_groups: [7] }, class: "input-clamp", required: true)
= text_field_tag :shared_group_id, nil, class: 'form-control', id: 'foo'
.form-group
= label_tag :shared_group_access, 'Max access level', class: 'label-bold'
.select-wrapper
= select_tag :share_group_access, options_for_select(GroupGroupLink.access_options, GroupGroupLink.default_access), class: 'form-control select-control'
= icon('chevron-down')
.form-text.text-muted.append-bottom-10
= link_to 'Read more', help_page_path('user/permissions'), class: 'vlink'
about role permissions
.form-group
= label_tag :expires_at, 'Access expiration date', class: 'label-bold'
.clearable-input
= text_field_tag :expires_at, nil, class: 'form-control js-access-expiration-date-groups', placeholder: 'Expiration date', id: 'expires_at_groups'
%i.clear-icon.js-clear-input
= submit_tag 'Share', class: 'btn btn-create'
- page_title "Members"
- can_manage_members = can?(current_user, :admin_group_member, @group)
.project-members-page.prepend-top-default
%h4
Members
%hr
- if can_manage_members
.project-members-new.append-bottom-default
%p.clearfix
Add new member to
.row.prepend-top-default
.col-lg-12
%h4
Group members
- if can_manage_members
%p
You can add a new member to
%strong= @group.name
= render "new_group_member"
or share it with another group.
- else
%p
Members can be added by project
%i Maintainers
or
%i Owners
.light
- if can_manage_members
%ul.nav-links.nav.nav-tabs.gitlab-tabs{ role: 'tablist' }
%li.nav-tab{ role: 'presentation' }
%a.nav-link.active{ href: '#add-member-pane', id: 'add-member-tab', data: { toggle: 'tab' }, role: 'tab' } Add member
- #if @project.allowed_to_share_with_group?
%li.nav-tab{ role: 'presentation' }
%a.nav-link{ href: '#share-with-group-pane', id: 'share-with-group-tab', data: { toggle: 'tab' }, role: 'tab' } Share with group
= render 'shared/members/requests', membership_source: @group, requesters: @requesters
.tab-content.gitlab-tab-content
.tab-pane.active{ id: 'add-member-pane', role: 'tabpanel' }
= render 'groups/group_members/new_group_member', tab_title: 'Add member'
.tab-pane{ id: 'share-with-group-pane', role: 'tabpanel' }
= render 'groups/group_members/new_shared_group', tab_title: 'Share with group'
.clearfix
%h5.member.existing-title
Existing members
.card
.card-header.flex-project-members-panel
%span.flex-project-title
Members with access to
%strong= @group.name
%span.badge= @members.total_count
= form_tag group_group_members_path(@group), method: :get, class: 'form-inline member-search-form flex-project-members-form' do
.form-group
.position-relative.append-right-8
= search_field_tag :search, params[:search], { placeholder: 'Find existing members by name', class: 'form-control', spellcheck: false }
%button.member-search-btn{ type: "submit", "aria-label" => "Submit search" }
= icon("search")
- if can_manage_members
= render 'shared/members/filter_2fa_dropdown'
= render 'shared/members/sort_dropdown'
%ul.content-list.members-list
= render partial: 'shared/members/member', collection: @members, as: :member
= paginate @members, theme: 'gitlab'
= render 'shared/members/requests', membership_source: @group, requesters: @requesters
.clearfix
%h5.member.existing-title
Existing members
.card
.card-header.flex-project-members-panel
%span.flex-project-title
Members with access to
%strong= @group.name
%span.badge= @members.total_count
= form_tag group_group_members_path(@group), method: :get, class: 'form-inline member-search-form flex-project-members-form' do
.form-group
.position-relative.append-right-8
= search_field_tag :search, params[:search], { placeholder: 'Find existing members by name', class: 'form-control', spellcheck: false }
%button.member-search-btn{ type: "submit", "aria-label" => "Submit search" }
= icon("search")
- if can_manage_members
= render 'shared/members/filter_2fa_dropdown'
= render 'shared/members/sort_dropdown'
%ul.content-list.members-list
= render partial: 'shared/members/member', collection: @members, as: :member
= paginate @members, theme: 'gitlab'
......@@ -51,6 +51,8 @@ constraints(::Constraints::GroupUrlConstrainer.new) do
delete :leave, on: :collection
end
resources :group_links, only: [:index, :create, :update, :destroy], constraints: { id: /\d+/ }
resources :uploads, only: [:create] do
collection do
get ":secret/:filename", action: :show, as: :show, constraints: { filename: %r{[^/]+} }
......
# frozen_string_literal: true
class CreateGroupGroupLinks < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
create_table :group_group_links do |t|
t.integer :shared_group_id
t.integer :shared_with_group_id
t.integer :group_access, default: 30, null: false
t.date :expires_at
end
add_concurrent_foreign_key :group_group_links,
:namespaces,
column: :shared_group_id,
on_delete: :cascade
add_concurrent_index :group_group_links, :shared_group_id
change_column_null :group_group_links, :shared_group_id, false
add_concurrent_foreign_key :group_group_links,
:namespaces,
column: :shared_with_group_id,
on_delete: :cascade
add_concurrent_index :group_group_links, :shared_with_group_id
change_column_null :group_group_links, :shared_with_group_id, false
add_concurrent_index :group_group_links,
[:shared_group_id, :shared_with_group_id],
unique: true,
name: 'index_group_group_links_on_shared_group_and_shared_with_group'
end
def down
drop_table :group_group_links
end
end
......@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20180826111825) do
ActiveRecord::Schema.define(version: 20180902070406) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
......@@ -949,6 +949,17 @@ ActiveRecord::Schema.define(version: 20180826111825) do
add_index "group_custom_attributes", ["group_id", "key"], name: "index_group_custom_attributes_on_group_id_and_key", unique: true, using: :btree
add_index "group_custom_attributes", ["key", "value"], name: "index_group_custom_attributes_on_key_and_value", using: :btree
create_table "group_group_links", force: :cascade do |t|
t.integer "shared_group_id", null: false
t.integer "shared_with_group_id", null: false
t.integer "group_access", default: 30, null: false
t.date "expires_at"
end
add_index "group_group_links", ["shared_group_id", "shared_with_group_id"], name: "index_group_group_links_on_shared_group_and_shared_with_group", unique: true, using: :btree
add_index "group_group_links", ["shared_group_id"], name: "index_group_group_links_on_shared_group_id", using: :btree
add_index "group_group_links", ["shared_with_group_id"], name: "index_group_group_links_on_shared_with_group_id", using: :btree
create_table "identities", force: :cascade do |t|
t.string "extern_uid"
t.string "provider"
......@@ -2318,6 +2329,8 @@ ActiveRecord::Schema.define(version: 20180826111825) do
add_foreign_key "gpg_signatures", "gpg_keys", on_delete: :nullify
add_foreign_key "gpg_signatures", "projects", 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", name: "fk_d3a0488427", on_delete: :cascade
add_foreign_key "group_group_links", "namespaces", column: "shared_with_group_id", name: "fk_2b2353ca49", on_delete: :cascade
add_foreign_key "import_export_uploads", "projects", on_delete: :cascade
add_foreign_key "internal_ids", "namespaces", name: "fk_162941d509", on_delete: :cascade
add_foreign_key "internal_ids", "projects", on_delete: :cascade
......
require 'spec_helper'
describe Groups::GroupLinksController do
let(:group) { create(:group, :public) }
# TODO(ifarkas): why does it need to be public?
let(:shared_group) { create(:group, :public) }
let(:user) { create(:user) }
before do
sign_in(user)
end
describe '#create' do
subject do
post(:create,
group_id: group,
shared_group_id: shared_group.id,
shared_group_access: GroupGroupLink.default_access)
end
context 'when user has access to group he want to link another group to' do
before do
group.add_developer(user)
shared_group.add_developer(user)
end
it 'links group with selected group' do
subject
expect(group.shared_groups).to include shared_group
end
it 'redirects to group links page' do
subject
expect(response).to(redirect_to(group_group_members_path(group)))
end
end
end
end
FactoryBot.define do
factory :group_group_link do
shared_group { group }
shared_with_group { group }
access_level { GroupMember::DEVELOPER }
expires_at nil
end
end
require 'spec_helper'
describe GroupGroupLink do
describe 'validation' do
let(:group) { create(:group) }
let(:shared_group) { create(:group) }
let!(:group_group_link) { create(:group_group_link, shared_group: shared_group, shared_with_group: group) }
it { is_expected.to validate_presence_of(:shared_group) }
it { is_expected.to validate_uniqueness_of(:shared_group_id).scoped_to(:shared_with_group_id).with_message(/already shared/) }
it { is_expected.to validate_presence_of(:shared_group) }
it { is_expected.to validate_presence_of(:group_access) }
it { is_expected.to validate_inclusion_of(:group_access).in_array(Gitlab::Access.values) }
# it 'does not allow a project to be shared with the group it is in' do
# project_group_link.group = group
#
# expect(project_group_link).not_to be_valid
# end
#
# it 'doesn not allow a project to be shared with an ancestor of the group it is in', :nested_groups do
# project_group_link.group = parent_group
#
# expect(project_group_link).not_to be_valid
# end
end
end
require 'spec_helper'
describe Groups::GroupLinks::CreateService, '#execute' do
let(:user) { create :user }
let(:group) { create :group }
let(:shared_group) { create :group }
let(:opts) do
{
shared_group_access: Gitlab::Access::DEVELOPER,
expires_at: nil
}
end
let(:subject) { described_class.new(group, user, opts) }
it 'adds group to another group' do
expect { subject.execute(group) }.to change { group.group_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.group_group_links.count }
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