Commit a41449a1 authored by Michael Kozono's avatar Michael Kozono

Merge branch '14729-group-deploy-keys' into 'master'

Backend for Group Deploy Keys

Closes #14729

See merge request gitlab-org/gitlab!38214
parents 2d485fed 6cacdd83
......@@ -64,6 +64,8 @@ class Group < Namespace
has_one :import_state, class_name: 'GroupImportState', inverse_of: :group
has_many :group_deploy_keys_groups, inverse_of: :group
has_many :group_deploy_keys, through: :group_deploy_keys_groups
has_many :group_deploy_tokens
has_many :deploy_tokens, through: :group_deploy_tokens
......
......@@ -3,9 +3,31 @@
class GroupDeployKey < Key
self.table_name = 'group_deploy_keys'
has_many :group_deploy_keys_groups, inverse_of: :group_deploy_key, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_many :groups, through: :group_deploy_keys_groups
validates :user, presence: true
def type
'DeployKey'
end
def group_deploy_keys_group_for(group)
group_deploy_keys_groups.find_by(group: group)
end
def can_be_edited_for?(user, group)
Ability.allowed?(user, :update_group_deploy_key, self) ||
Ability.allowed?(
user,
:update_group_deploy_key_for_group,
group_deploy_keys_group_for(group)
)
end
def group_deploy_keys_groups_for_user(user)
group_deploy_keys_groups.select do |group_deploy_keys_group|
Ability.allowed?(user, :read_group, group_deploy_keys_group.group)
end
end
end
# frozen_string_literal: true
class GroupDeployKeysGroup < ApplicationRecord
belongs_to :group, inverse_of: :group_deploy_keys_groups
belongs_to :group_deploy_key, inverse_of: :group_deploy_keys_groups
validates :group_deploy_key, presence: true
validates :group_deploy_key_id, uniqueness: { scope: [:group_id], message: "already exists in group" }
validates :group_id, presence: true
end
......@@ -104,6 +104,7 @@ class User < ApplicationRecord
# Profile
has_many :keys, -> { regular_keys }, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_many :deploy_keys, -> { where(type: 'DeployKey') }, dependent: :nullify # rubocop:disable Cop/ActiveRecordDependent
has_many :group_deploy_keys
has_many :gpg_keys
has_many :emails, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
......
# frozen_string_literal: true
class GroupDeployKeyPolicy < BasePolicy
with_options scope: :subject, score: 0
condition(:user_owns_group_deploy_key) { @subject.user_id == @user.id }
rule { user_owns_group_deploy_key }.enable :update_group_deploy_key
end
# frozen_string_literal: true
class GroupDeployKeysGroupPolicy < BasePolicy
with_options scope: :subject, score: 0
delegate { @subject.group }
condition(:user_is_group_owner) { @subject.group.has_owner?(@user) }
rule { user_is_group_owner }.enable :update_group_deploy_key_for_group
end
# frozen_string_literal: true
class GroupBasicEntity < Grape::Entity
include RequestAwareEntity
expose :id
expose :name
expose :full_path
expose :full_name
end
# frozen_string_literal: true
class GroupDeployKeyEntity < Grape::Entity
expose :id
expose :user_id
expose :title
expose :fingerprint
expose :fingerprint_sha256
expose :created_at
expose :updated_at
expose :group_deploy_keys_groups, using: GroupDeployKeysGroupEntity do |group_deploy_key|
group_deploy_key.group_deploy_keys_groups_for_user(options[:user])
end
expose :can_edit do |group_deploy_key|
group_deploy_key.can_be_edited_for?(options[:user], options[:group])
end
end
# frozen_string_literal: true
class GroupDeployKeySerializer < BaseSerializer
entity GroupDeployKeyEntity
end
# frozen_string_literal: true
class GroupDeployKeysGroupEntity < Grape::Entity
expose :can_push
expose :group, using: GroupBasicEntity
end
# frozen_string_literal: true
FactoryBot.define do
factory :group_deploy_keys_group do
group_deploy_key
group
can_push { true }
end
end
......@@ -4,8 +4,82 @@ require 'spec_helper'
RSpec.describe GroupDeployKey do
it { is_expected.to validate_presence_of(:user) }
it { is_expected.to belong_to(:user) }
it { is_expected.to have_many(:groups) }
let_it_be(:group_deploy_key) { create(:group_deploy_key) }
let_it_be(:group) { create(:group) }
it 'is of type DeployKey' do
expect(build(:group_deploy_key).type).to eq('DeployKey')
end
describe '#group_deploy_keys_group_for' do
subject { group_deploy_key.group_deploy_keys_group_for(group) }
context 'when this group deploy key is linked to a given group' do
it 'returns the relevant group_deploy_keys_group association' do
group_deploy_keys_group = create(:group_deploy_keys_group, group: group, group_deploy_key: group_deploy_key)
expect(subject).to eq(group_deploy_keys_group)
end
end
context 'when this group deploy key is not linked to a given group' do
it { is_expected.to be_nil }
end
end
describe '#can_be_edited_for' do
let_it_be(:user) { create(:user) }
subject { group_deploy_key.can_be_edited_for?(user, group) }
context 'when a given user has the :update_group_deploy_key permission for that key' do
it 'is true' do
allow(Ability).to receive(:allowed?).with(user, :update_group_deploy_key, group_deploy_key).and_return(true)
expect(subject).to be_truthy
end
end
context 'when a given user does not have the :update_group_deploy_key permission for that key' do
before do
allow(Ability).to receive(:allowed?).with(user, :update_group_deploy_key, group_deploy_key).and_return(false)
end
it 'is true when this user has the :update_group_deploy_key_for_group permission for this group' do
allow(Ability).to receive(:allowed?).with(user, :update_group_deploy_key_for_group, group_deploy_key.group_deploy_keys_group_for(group)).and_return(true)
expect(subject).to be_truthy
end
it 'is false when this user does not have the :update_group_deploy_key_for_group permission for this group' do
allow(Ability).to receive(:allowed?).with(user, :update_group_deploy_key_for_group, group_deploy_key.group_deploy_keys_group_for(group)).and_return(false)
expect(subject).to be_falsey
end
end
end
describe '#group_deploy_keys_groups_for_user' do
let_it_be(:user) { create(:user) }
context 'when a group has a group deploy key' do
let_it_be(:expected_association) { create(:group_deploy_keys_group, group: group, group_deploy_key: group_deploy_key) }
it 'returns the related group_deploy_keys_group association when the user can read the group' do
allow(Ability).to receive(:allowed?).with(user, :read_group, group).and_return(true)
expect(group_deploy_key.group_deploy_keys_groups_for_user(user))
.to contain_exactly(expected_association)
end
it 'does not return the related group_deploy_keys_group association when the user cannot read the group' do
allow(Ability).to receive(:allowed?).with(user, :read_group, group).and_return(false)
expect(group_deploy_key.group_deploy_keys_groups_for_user(user)).to be_empty
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe GroupDeployKeysGroup do
describe "Associations" do
it { is_expected.to belong_to(:group_deploy_key) }
it { is_expected.to belong_to(:group) }
end
describe "Validation" do
it { is_expected.to validate_presence_of(:group_id) }
it { is_expected.to validate_presence_of(:group_deploy_key) }
end
end
......@@ -26,6 +26,7 @@ RSpec.describe Group do
it { is_expected.to have_many(:container_repositories) }
it { is_expected.to have_many(:milestones) }
it { is_expected.to have_many(:iterations) }
it { is_expected.to have_many(:group_deploy_keys) }
describe '#members & #requesters' do
let(:requester) { create(:user) }
......
......@@ -76,6 +76,7 @@ RSpec.describe User do
it { is_expected.to have_many(:groups) }
it { is_expected.to have_many(:keys).dependent(:destroy) }
it { is_expected.to have_many(:deploy_keys).dependent(:nullify) }
it { is_expected.to have_many(:group_deploy_keys) }
it { is_expected.to have_many(:events).dependent(:delete_all) }
it { is_expected.to have_many(:issues).dependent(:destroy) }
it { is_expected.to have_many(:notes).dependent(:destroy) }
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe GroupDeployKeyPolicy do
subject { described_class.new(user, group_deploy_key) }
let_it_be(:user) { create(:user) }
describe 'edit a group deploy key' do
context 'when the user does not own the group deploy key' do
let(:group_deploy_key) { create(:group_deploy_key) }
it { is_expected.to be_disallowed(:update_group_deploy_key) }
end
context 'when the user owns the group deploy key' do
let(:group_deploy_key) { create(:group_deploy_key, user: user) }
before do
user.reload
end
it { is_expected.to be_allowed(:update_group_deploy_key) }
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe GroupDeployKeysGroupPolicy do
subject { described_class.new(user, group_deploy_keys_group) }
let_it_be(:user) { create(:user) }
let_it_be(:group) { create(:group) }
let_it_be(:group_deploy_key) { create(:group_deploy_key) }
let(:group_deploy_keys_group) { create(:group_deploy_keys_group, group: group, group_deploy_key: group_deploy_key) }
describe 'edit a group deploy key for a given group' do
it 'is allowed when the user is an owner of this group' do
group.add_owner(user)
expect(subject).to be_allowed(:update_group_deploy_key_for_group)
end
it 'is not allowed when the user is not an owner of this group' do
expect(subject).to be_disallowed(:update_group_deploy_key_for_group)
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe GroupDeployKeyEntity do
include RequestAwareEntity
let(:user) { create(:user) }
let(:group) { create(:group) }
let(:group_deploy_key) { create(:group_deploy_key) }
let(:options) { { user: user } }
let(:entity) { described_class.new(group_deploy_key, options) }
before do
group.group_deploy_keys << group_deploy_key
end
describe 'returns group deploy keys with a group a user can read' do
let(:expected_result) do
{
id: group_deploy_key.id,
user_id: group_deploy_key.user_id,
title: group_deploy_key.title,
fingerprint: group_deploy_key.fingerprint,
fingerprint_sha256: group_deploy_key.fingerprint_sha256,
created_at: group_deploy_key.created_at,
updated_at: group_deploy_key.updated_at,
can_edit: false,
group_deploy_keys_groups: [
{
can_push: false,
group:
{
id: group.id,
name: group.name,
full_path: group.full_path,
full_name: group.full_name
}
}
]
}
end
it { expect(entity.as_json).to eq(expected_result) }
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