Improve group visibility level feature

This commit is contained in:
Zeger-Jan van de Weg 2016-03-18 13:28:16 +01:00 committed by Felipe Artur
parent 0a7f716119
commit b959ae553b
21 changed files with 118 additions and 115 deletions

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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|

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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 }

View file

@ -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 ) }

View file

@ -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 ) }
before do
internal_group.add_user(user, Gitlab::Access::MASTER)
create(:project, :internal, group: internal_group, name: 'B', path: 'B')
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)
end
it "cant downgrade permission level" do
expect(service.execute).to be_falsy
expect(internal_group.errors.count).to eq(2)
end
end
it "cant downgrade permission level" do
expect(service.execute).to be_falsy
expect(internal_group.errors.count).to eq(1)
end
end
end
end