Improve group visibility level feature
This commit is contained in:
parent
0a7f716119
commit
b959ae553b
21 changed files with 118 additions and 115 deletions
|
@ -9,7 +9,7 @@ class Groups::ApplicationController < ApplicationController
|
|||
end
|
||||
|
||||
def authorize_read_group!
|
||||
unless @group and can?(current_user, :read_group, @group)
|
||||
unless @group && can?(current_user, :read_group, @group)
|
||||
if current_user.nil?
|
||||
return authenticate_user!
|
||||
else
|
||||
|
|
|
@ -105,17 +105,6 @@ class GroupsController < Groups::ApplicationController
|
|||
@projects ||= ProjectsFinder.new.execute(current_user, group: group).sorted_by_activity
|
||||
end
|
||||
|
||||
# Dont allow unauthorized access to group
|
||||
def authorize_read_group!
|
||||
unless can?(current_user, :read_group, @group)
|
||||
if current_user.nil?
|
||||
return authenticate_user!
|
||||
else
|
||||
return render_404
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def authorize_create_group!
|
||||
unless can?(current_user, :create_group, nil)
|
||||
return render_404
|
||||
|
|
|
@ -85,7 +85,6 @@ module VisibilityLevelHelper
|
|||
end
|
||||
|
||||
def skip_level?(form_model, level)
|
||||
form_model.is_a?(Project) &&
|
||||
!form_model.visibility_level_allowed?(level)
|
||||
form_model.is_a?(Project) && !form_model.visibility_level_allowed?(level)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -295,7 +295,7 @@ class Ability
|
|||
end
|
||||
|
||||
def can_read_group?(user, group)
|
||||
user.admin? || group.public? || (group.internal? && !user.external?) || group.users.include?(user) ||
|
||||
user.admin? || group.public? || (group.internal? && !user.external?) || group.users.include?(user) ||
|
||||
ProjectsFinder.new.execute(user, group: group).any?
|
||||
end
|
||||
|
||||
|
|
|
@ -29,6 +29,8 @@ class Group < Namespace
|
|||
has_many :shared_projects, through: :project_group_links, source: :project
|
||||
|
||||
validate :avatar_type, if: ->(user) { user.avatar.present? && user.avatar_changed? }
|
||||
validate :visibility_level_allowed_by_projects
|
||||
|
||||
validates :avatar, file_size: { maximum: 200.kilobytes.to_i }
|
||||
|
||||
mount_uploader :avatar, AvatarUploader
|
||||
|
@ -80,6 +82,26 @@ class Group < Namespace
|
|||
visibility_level
|
||||
end
|
||||
|
||||
def visibility_level_allowed_by_projects
|
||||
unless visibility_level_allowed?
|
||||
level_name = Gitlab::VisibilityLevel.level_name(visibility_level).downcase
|
||||
self.errors.add(:visibility_level, "#{level_name} is not allowed since there are projects with higher visibility.")
|
||||
end
|
||||
end
|
||||
|
||||
def visibility_level_allowed?
|
||||
projects_visibility = self.projects.pluck(:visibility_level)
|
||||
|
||||
allowed_by_projects = projects_visibility.none? { |project_visibility| self.visibility_level < project_visibility }
|
||||
|
||||
unless allowed_by_projects
|
||||
level_name = Gitlab::VisibilityLevel.level_name(visibility_level).downcase
|
||||
self.errors.add(:visibility_level, "#{level_name} is not allowed since there are projects with higher visibility.")
|
||||
end
|
||||
|
||||
allowed_by_projects
|
||||
end
|
||||
|
||||
def avatar_url(size = nil)
|
||||
if avatar.present?
|
||||
[gitlab_config.url, avatar.url].join
|
||||
|
|
|
@ -73,7 +73,7 @@ class Project < ActiveRecord::Base
|
|||
update_column(:last_activity_at, self.created_at)
|
||||
end
|
||||
|
||||
# update visibility_levet of forks
|
||||
# update visibility_level of forks
|
||||
after_update :update_forks_visibility_level
|
||||
def update_forks_visibility_level
|
||||
return unless visibility_level < visibility_level_was
|
||||
|
@ -197,6 +197,7 @@ class Project < ActiveRecord::Base
|
|||
validate :avatar_type,
|
||||
if: ->(project) { project.avatar.present? && project.avatar_changed? }
|
||||
validates :avatar, file_size: { maximum: 200.kilobytes.to_i }
|
||||
validate :visibility_level_allowed_in_group
|
||||
|
||||
add_authentication_token_field :runners_token
|
||||
before_save :ensure_runners_token
|
||||
|
@ -446,6 +447,12 @@ class Project < ActiveRecord::Base
|
|||
errors[:base] << ("Can't check your ability to create project")
|
||||
end
|
||||
|
||||
def visibility_level_allowed_in_group
|
||||
unless visibility_level_allowed?
|
||||
self.errors.add(:visibility_level, "#{self.visibility_level} is not allowed in a #{self.group.visibility_level} group.")
|
||||
end
|
||||
end
|
||||
|
||||
def to_param
|
||||
path
|
||||
end
|
||||
|
@ -961,9 +968,14 @@ class Project < ActiveRecord::Base
|
|||
issues.opened.count
|
||||
end
|
||||
|
||||
def visibility_level_allowed?(level)
|
||||
allowed_by_forks = forked? ? Gitlab::VisibilityLevel.allowed_fork_levels(forked_from_project.visibility_level).include?(level.to_i) : true
|
||||
allowed_by_groups = group.present? ? level.to_i <= group.visibility_level : true
|
||||
def visibility_level_allowed?(level = self.visibility_level)
|
||||
allowed_by_forks = if forked?
|
||||
Gitlab::VisibilityLevel.allowed_fork_levels(forked_from_project.visibility_level).include?(level)
|
||||
else
|
||||
true
|
||||
end
|
||||
|
||||
allowed_by_groups = group.present? ? level <= group.visibility_level : true
|
||||
|
||||
allowed_by_forks && allowed_by_groups
|
||||
end
|
||||
|
|
|
@ -8,18 +8,13 @@ module Groups
|
|||
|
||||
private
|
||||
|
||||
def visibility_allowed_for_user?(level)
|
||||
def visibility_allowed_for_user?
|
||||
level = group.visibility_level
|
||||
allowed_by_user = Gitlab::VisibilityLevel.allowed_for?(current_user, level)
|
||||
@group.errors.add(:visibility_level, "You are not authorized to set this permission level.") unless allowed_by_user
|
||||
|
||||
group.errors.add(:visibility_level, "#{level} has been restricted by your GitLab administrator.") unless allowed_by_user
|
||||
|
||||
allowed_by_user
|
||||
end
|
||||
|
||||
def visibility_allowed_for_project?(level)
|
||||
projects_visibility = group.projects.pluck(:visibility_level)
|
||||
|
||||
allowed_by_projects = !projects_visibility.any? { |project_visibility| level.to_i < project_visibility }
|
||||
@group.errors.add(:visibility_level, "Cannot be changed. There are projects with higher visibility permissions.") unless allowed_by_projects
|
||||
allowed_by_projects
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2,14 +2,16 @@ module Groups
|
|||
class CreateService < Groups::BaseService
|
||||
def initialize(user, params = {})
|
||||
@current_user, @params = user, params.dup
|
||||
@group = Group.new(@params)
|
||||
end
|
||||
|
||||
def execute
|
||||
return @group unless visibility_allowed_for_user?(@params[:visibility_level])
|
||||
@group = Group.new(params)
|
||||
|
||||
return @group unless visibility_allowed_for_user?
|
||||
|
||||
@group.name = @group.path.dup unless @group.name
|
||||
@group.save
|
||||
@group.add_owner(@current_user)
|
||||
@group.add_owner(current_user)
|
||||
@group
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,20 +1,15 @@
|
|||
#Checks visibility level permission check before updating a group
|
||||
#Do not allow to put Group visibility level smaller than its projects
|
||||
#Do not allow unauthorized permission levels
|
||||
# Checks visibility level permission check before updating a group
|
||||
# Do not allow to put Group visibility level smaller than its projects
|
||||
# Do not allow unauthorized permission levels
|
||||
|
||||
module Groups
|
||||
class UpdateService < Groups::BaseService
|
||||
def execute
|
||||
return false unless visibility_level_allowed?(params[:visibility_level])
|
||||
group.update_attributes(params)
|
||||
end
|
||||
group.assign_attributes(params)
|
||||
|
||||
private
|
||||
return false unless visibility_allowed_for_user?
|
||||
|
||||
def visibility_level_allowed?(level)
|
||||
return true unless level.present?
|
||||
|
||||
visibility_allowed_for_project?(level) && visibility_allowed_for_user?(level)
|
||||
group.save
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -9,13 +9,8 @@ module Projects
|
|||
|
||||
@project = Project.new(params)
|
||||
|
||||
# Make sure that the user is allowed to use the specified visibility
|
||||
# level
|
||||
|
||||
unless visibility_level_allowed?
|
||||
deny_visibility_level(@project)
|
||||
return @project
|
||||
end
|
||||
# Make sure that the user is allowed to use the specified visibility level
|
||||
return @project unless visibility_level_allowed?
|
||||
|
||||
# Set project name from path
|
||||
if @project.name.present? && @project.path.present?
|
||||
|
@ -55,9 +50,7 @@ module Projects
|
|||
@project.save
|
||||
|
||||
if @project.persisted? && !@project.import?
|
||||
unless @project.create_repository
|
||||
raise 'Failed to create repository'
|
||||
end
|
||||
raise 'Failed to create repository' unless @project.create_repository
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -17,8 +17,7 @@
|
|||
.cover-title
|
||||
%h1
|
||||
= @group.name
|
||||
|
||||
%span.visibility-icon.has_tooltip{data: { container: 'body', placement: 'left' }, title: "#{group_visibility_description(@group)}"}
|
||||
%span.visibility-icon.has_tooltip{ data: { container: 'body', placement: 'left' }, title: group_visibility_description(@group) }
|
||||
= visibility_level_icon(@group.visibility_level, fw: false)
|
||||
|
||||
.cover-desc.username
|
||||
|
|
|
@ -85,7 +85,7 @@ module API
|
|||
end
|
||||
|
||||
class Group < Grape::Entity
|
||||
expose :id, :name, :path, :description
|
||||
expose :id, :name, :path, :description, :visibility_level
|
||||
expose :avatar_url
|
||||
|
||||
expose :web_url do |group, options|
|
||||
|
|
|
@ -9,8 +9,8 @@ module Gitlab
|
|||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
scope :public_only, -> { where(visibility_level: PUBLIC) }
|
||||
scope :public_and_internal_only, -> { where(visibility_level: [PUBLIC, INTERNAL] ) }
|
||||
scope :public_only, -> { where(visibility_level: PUBLIC) }
|
||||
scope :public_and_internal_only, -> { where(visibility_level: [PUBLIC, INTERNAL] ) }
|
||||
end
|
||||
|
||||
PRIVATE = 0 unless const_defined?(:PRIVATE)
|
||||
|
|
|
@ -3,5 +3,17 @@ FactoryGirl.define do
|
|||
sequence(:name) { |n| "group#{n}" }
|
||||
path { name.downcase.gsub(/\s/, '_') }
|
||||
type 'Group'
|
||||
|
||||
trait :public do
|
||||
visibility_level Gitlab::VisibilityLevel::PUBLIC
|
||||
end
|
||||
|
||||
trait :internal do
|
||||
visibility_level Gitlab::VisibilityLevel::INTERNAL
|
||||
end
|
||||
|
||||
trait :private do
|
||||
visibility_level Gitlab::VisibilityLevel::PRIVATE
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2,11 +2,11 @@ require 'spec_helper'
|
|||
|
||||
describe GroupsFinder do
|
||||
describe '#execute' do
|
||||
let(:user) { create(:user) }
|
||||
let!(:private_group) { create(:group, visibility_level: 0) }
|
||||
let!(:internal_group) { create(:group, visibility_level: 10) }
|
||||
let!(:public_group) { create(:group, visibility_level: 20) }
|
||||
let(:finder) { described_class.new }
|
||||
let(:user) { create(:user) }
|
||||
let!(:private_group) { create(:group, :private) }
|
||||
let!(:internal_group) { create(:group, :internal) }
|
||||
let!(:public_group) { create(:group, :public) }
|
||||
let(:finder) { described_class.new }
|
||||
|
||||
describe 'execute' do
|
||||
describe 'without a user' do
|
||||
|
@ -23,7 +23,8 @@ describe GroupsFinder do
|
|||
end
|
||||
|
||||
context 'external user' do
|
||||
before { user.update_attribute(external: true) }
|
||||
let(:user) { create(:user, external: true) }
|
||||
|
||||
it { is_expected.to eq([public_group]) }
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,24 +1,17 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe PersonalProjectsFinder do
|
||||
let(:source_user) { create(:user) }
|
||||
let(:current_user) { create(:user) }
|
||||
|
||||
let(:finder) { described_class.new(source_user) }
|
||||
|
||||
let!(:public_project) do
|
||||
create(:project, :public, namespace: source_user.namespace, name: 'A',
|
||||
path: 'A')
|
||||
end
|
||||
let(:source_user) { create(:user) }
|
||||
let(:current_user) { create(:user) }
|
||||
let(:finder) { described_class.new(source_user) }
|
||||
let!(:public_project) { create(:project, :public, namespace: source_user.namespace) }
|
||||
|
||||
let!(:private_project) do
|
||||
create(:project, :private, namespace: source_user.namespace, name: 'B',
|
||||
path: 'B')
|
||||
create(:project, :private, namespace: source_user.namespace, path: 'mepmep')
|
||||
end
|
||||
|
||||
let!(:internal_project) do
|
||||
create(:project, :internal, namespace: source_user.namespace, name: 'c',
|
||||
path: 'C')
|
||||
create(:project, :internal, namespace: source_user.namespace, path: 'C')
|
||||
end
|
||||
|
||||
before do
|
||||
|
|
|
@ -66,13 +66,8 @@ describe VisibilityLevelHelper do
|
|||
|
||||
describe "skip_level?" do
|
||||
describe "forks" do
|
||||
let(:project) { create(:project, :internal) }
|
||||
let(:fork_project) { create(:forked_project_with_submodules) }
|
||||
|
||||
before do
|
||||
fork_project.build_forked_project_link(forked_to_project_id: fork_project.id, forked_from_project_id: project.id)
|
||||
fork_project.save
|
||||
end
|
||||
let(:project) { create(:project, :internal) }
|
||||
let(:fork_project) { create(:project, forked_from_project: project) }
|
||||
|
||||
it "skips levels" do
|
||||
expect(skip_level?(fork_project, Gitlab::VisibilityLevel::PUBLIC)).to be_truthy
|
||||
|
|
|
@ -57,18 +57,18 @@ describe Group, models: true do
|
|||
end
|
||||
|
||||
describe 'scopes' do
|
||||
let!(:private_group) { create(:group, visibility_level: 0) }
|
||||
let!(:internal_group) { create(:group, visibility_level: 10) }
|
||||
let!(:public_group) { create(:group, visibility_level: 20) }
|
||||
let!(:private_group) { create(:group, :private) }
|
||||
let!(:internal_group) { create(:group, :internal) }
|
||||
let!(:public_group) { create(:group, :public) }
|
||||
|
||||
describe 'public_only' do
|
||||
subject { described_class.public_only }
|
||||
subject { described_class.public_only.to_a }
|
||||
|
||||
it{ is_expected.to eq([public_group]) }
|
||||
end
|
||||
|
||||
describe 'public_and_internal_only' do
|
||||
subject { described_class.public_and_internal_only }
|
||||
subject { described_class.public_and_internal_only.to_a }
|
||||
|
||||
it{ is_expected.to eq([public_group, internal_group]) }
|
||||
end
|
||||
|
|
|
@ -571,12 +571,8 @@ describe Project, models: true do
|
|||
end
|
||||
|
||||
context 'when checking on forked project' do
|
||||
let(:forked_project) { create :forked_project_with_submodules }
|
||||
|
||||
before do
|
||||
forked_project.build_forked_project_link(forked_to_project_id: forked_project.id, forked_from_project_id: project.id)
|
||||
forked_project.save
|
||||
end
|
||||
let(:project) { create(:project, :internal) }
|
||||
let(:forked_project) { create(:project, forked_from_project: project) }
|
||||
|
||||
it { expect(forked_project.visibility_level_allowed?(Gitlab::VisibilityLevel::PRIVATE)).to be_truthy }
|
||||
it { expect(forked_project.visibility_level_allowed?(Gitlab::VisibilityLevel::INTERNAL)).to be_truthy }
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Groups::CreateService, services: true do
|
||||
let!(:user) { create(:user) }
|
||||
let!(:group_params) { { path: "group_path", visibility_level: Gitlab::VisibilityLevel::PUBLIC } }
|
||||
let!(:user) { create(:user) }
|
||||
let!(:group_params) { { path: "group_path", visibility_level: Gitlab::VisibilityLevel::PUBLIC } }
|
||||
|
||||
describe "execute" do
|
||||
let!(:service) { described_class.new(user, group_params ) }
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Groups::UpdateService, services: true do
|
||||
let!(:user) { create(:user) }
|
||||
let!(:private_group) { create(:group, visibility_level: Gitlab::VisibilityLevel::PRIVATE) }
|
||||
let!(:internal_group) { create(:group, visibility_level: Gitlab::VisibilityLevel::INTERNAL) }
|
||||
let!(:public_group) { create(:group, visibility_level: Gitlab::VisibilityLevel::PUBLIC) }
|
||||
let!(:user) { create(:user) }
|
||||
let!(:private_group) { create(:group, :private) }
|
||||
let!(:internal_group) { create(:group, :internal) }
|
||||
let!(:public_group) { create(:group, :public) }
|
||||
|
||||
describe "execute" do
|
||||
context "project visibility_level validation" do
|
||||
|
@ -14,28 +14,28 @@ describe Groups::UpdateService, services: true do
|
|||
|
||||
before do
|
||||
public_group.add_user(user, Gitlab::Access::MASTER)
|
||||
create(:project, :public, group: public_group, name: 'B', path: 'B')
|
||||
create(:project, :public, group: public_group)
|
||||
end
|
||||
|
||||
it "cant downgrade permission level" do
|
||||
expect(service.execute).to be_falsy
|
||||
expect(public_group.errors.count).to eq(1)
|
||||
expect(public_group.errors.count).to eq(2)
|
||||
end
|
||||
end
|
||||
|
||||
context "internal group with internal project" do
|
||||
let!(:service) { described_class.new(internal_group, user, visibility_level: Gitlab::VisibilityLevel::PRIVATE ) }
|
||||
context "internal group with internal project" do
|
||||
let!(:service) { described_class.new(internal_group, user, visibility_level: Gitlab::VisibilityLevel::PRIVATE ) }
|
||||
|
||||
before do
|
||||
internal_group.add_user(user, Gitlab::Access::MASTER)
|
||||
create(:project, :internal, group: internal_group, name: 'B', path: 'B')
|
||||
end
|
||||
before do
|
||||
internal_group.add_user(user, Gitlab::Access::MASTER)
|
||||
create(:project, :internal, group: internal_group)
|
||||
end
|
||||
|
||||
it "cant downgrade permission level" do
|
||||
expect(service.execute).to be_falsy
|
||||
expect(internal_group.errors.count).to eq(1)
|
||||
it "cant downgrade permission level" do
|
||||
expect(service.execute).to be_falsy
|
||||
expect(internal_group.errors.count).to eq(2)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
Loading…
Reference in a new issue