Commit e65071a6 authored by Dmytro Zaporozhets's avatar Dmytro Zaporozhets

Merge branch 'group-wiki-policies' into 'master'

Add policies for group wikis

See merge request gitlab-org/gitlab!29176
parents f66aaf08 95df97da
...@@ -475,6 +475,16 @@ class Group < Namespace ...@@ -475,6 +475,16 @@ class Group < Namespace
false false
end end
def wiki_access_level
# TODO: Remove this method once we implement group-level features.
# https://gitlab.com/gitlab-org/gitlab/-/issues/208412
if Feature.enabled?(:group_wiki, self)
ProjectFeature::ENABLED
else
ProjectFeature::DISABLED
end
end
private private
def update_two_factor_requirement def update_two_factor_requirement
......
# frozen_string_literal: true # frozen_string_literal: true
class ProjectPolicy module CrudPolicyHelpers
module ClassMethods extend ActiveSupport::Concern
class_methods do
def create_read_update_admin_destroy(name) def create_read_update_admin_destroy(name)
[ [
:"read_#{name}", :"read_#{name}",
......
# frozen_string_literal: true # frozen_string_literal: true
class GroupPolicy < BasePolicy class GroupPolicy < BasePolicy
include CrudPolicyHelpers
include FindGroupProjects include FindGroupProjects
desc "Group is public" desc "Group is public"
...@@ -42,15 +43,23 @@ class GroupPolicy < BasePolicy ...@@ -42,15 +43,23 @@ class GroupPolicy < BasePolicy
@subject.subgroup_creation_level == ::Gitlab::Access::MAINTAINER_SUBGROUP_ACCESS @subject.subgroup_creation_level == ::Gitlab::Access::MAINTAINER_SUBGROUP_ACCESS
end end
desc "Group has wiki disabled"
condition(:wiki_disabled, score: 32) { !feature_available?(:wiki) }
rule { public_group }.policy do rule { public_group }.policy do
enable :read_group enable :read_group
enable :read_package enable :read_package
enable :read_wiki
end end
rule { logged_in_viewable }.enable :read_group rule { logged_in_viewable }.policy do
enable :read_group
enable :read_wiki
end
rule { guest }.policy do rule { guest }.policy do
enable :read_group enable :read_group
enable :read_wiki
enable :upload_file enable :upload_file
end end
...@@ -78,10 +87,12 @@ class GroupPolicy < BasePolicy ...@@ -78,10 +87,12 @@ class GroupPolicy < BasePolicy
enable :create_metrics_dashboard_annotation enable :create_metrics_dashboard_annotation
enable :delete_metrics_dashboard_annotation enable :delete_metrics_dashboard_annotation
enable :update_metrics_dashboard_annotation enable :update_metrics_dashboard_annotation
enable :create_wiki
end end
rule { reporter }.policy do rule { reporter }.policy do
enable :read_container_image enable :read_container_image
enable :download_wiki_code
enable :admin_label enable :admin_label
enable :admin_list enable :admin_list
enable :admin_issue enable :admin_issue
...@@ -100,6 +111,7 @@ class GroupPolicy < BasePolicy ...@@ -100,6 +111,7 @@ class GroupPolicy < BasePolicy
enable :destroy_deploy_token enable :destroy_deploy_token
enable :read_deploy_token enable :read_deploy_token
enable :create_deploy_token enable :create_deploy_token
enable :admin_wiki
end end
rule { owner }.policy do rule { owner }.policy do
...@@ -145,6 +157,11 @@ class GroupPolicy < BasePolicy ...@@ -145,6 +157,11 @@ class GroupPolicy < BasePolicy
rule { maintainer & can?(:create_projects) }.enable :transfer_projects rule { maintainer & can?(:create_projects) }.enable :transfer_projects
rule { wiki_disabled }.policy do
prevent(*create_read_update_admin_destroy(:wiki))
prevent(:download_wiki_code)
end
def access_level def access_level
return GroupMember::NO_ACCESS if @user.nil? return GroupMember::NO_ACCESS if @user.nil?
...@@ -154,6 +171,21 @@ class GroupPolicy < BasePolicy ...@@ -154,6 +171,21 @@ class GroupPolicy < BasePolicy
def lookup_access_level! def lookup_access_level!
@subject.max_member_access_for_user(@user) @subject.max_member_access_for_user(@user)
end end
# TODO: Extract this into a helper shared with ProjectPolicy, once we implement group-level features.
# https://gitlab.com/gitlab-org/gitlab/-/issues/208412
def feature_available?(feature)
return false unless feature == :wiki
case @subject.wiki_access_level
when ProjectFeature::DISABLED
false
when ProjectFeature::PRIVATE
admin? || access_level >= ProjectFeature.required_minimum_access_level(feature)
else
true
end
end
end end
GroupPolicy.prepend_if_ee('EE::GroupPolicy') GroupPolicy.prepend_if_ee('EE::GroupPolicy')
...@@ -5,7 +5,7 @@ class IssuePolicy < IssuablePolicy ...@@ -5,7 +5,7 @@ class IssuePolicy < IssuablePolicy
# Make sure to sync this class checks with issue.rb to avoid security problems. # Make sure to sync this class checks with issue.rb to avoid security problems.
# Check commit 002ad215818450d2cbbc5fa065850a953dc7ada8 for more information. # Check commit 002ad215818450d2cbbc5fa065850a953dc7ada8 for more information.
extend ProjectPolicy::ClassMethods include CrudPolicyHelpers
desc "User can read confidential issues" desc "User can read confidential issues"
condition(:can_read_confidential) do condition(:can_read_confidential) do
......
# frozen_string_literal: true # frozen_string_literal: true
class ProjectPolicy < BasePolicy class ProjectPolicy < BasePolicy
extend ClassMethods include CrudPolicyHelpers
READONLY_FEATURES_WHEN_ARCHIVED = %i[ READONLY_FEATURES_WHEN_ARCHIVED = %i[
issue issue
......
...@@ -655,4 +655,26 @@ describe GroupPolicy do ...@@ -655,4 +655,26 @@ describe GroupPolicy do
end end
end end
end end
it_behaves_like 'model with wiki policies' do
let(:container) { create(:group) }
def set_access_level(access_level)
allow(container).to receive(:wiki_access_level).and_return(access_level)
end
before do
stub_feature_flags(group_wiki: true)
end
context 'when the feature flag is disabled' do
before do
stub_feature_flags(group_wiki: false)
end
it 'does not include the wiki permissions' do
expect_disallowed(*permissions)
end
end
end
end end
...@@ -121,147 +121,11 @@ describe ProjectPolicy do ...@@ -121,147 +121,11 @@ describe ProjectPolicy do
expect(Ability).not_to be_allowed(user, :read_issue, project) expect(Ability).not_to be_allowed(user, :read_issue, project)
end end
context 'wiki feature' do it_behaves_like 'model with wiki policies' do
let(:permissions) { %i(read_wiki create_wiki update_wiki admin_wiki download_wiki_code) } let(:container) { project }
subject { described_class.new(owner, project) } def set_access_level(access_level)
project.project_feature.update_attribute(:wiki_access_level, access_level)
context 'when the feature is disabled' do
before do
project.project_feature.update_attribute(:wiki_access_level, ProjectFeature::DISABLED)
end
it 'does not include the wiki permissions' do
expect_disallowed(*permissions)
end
context 'when there is an external wiki' do
it 'does not include the wiki permissions' do
allow(project).to receive(:has_external_wiki?).and_return(true)
expect_disallowed(*permissions)
end
end
end
describe 'read_wiki' do
subject { described_class.new(user, project) }
member_roles = %i[guest developer]
stranger_roles = %i[anonymous non_member]
user_roles = stranger_roles + member_roles
# When a user is anonymous, their `current_user == nil`
let(:user) { create(:user) unless user_role == :anonymous }
before do
project.visibility = project_visibility
project.project_feature.update_attribute(:wiki_access_level, wiki_access_level)
project.add_user(user, user_role) if member_roles.include?(user_role)
end
title = ->(project_visibility, wiki_access_level, user_role) do
[
"project is #{Gitlab::VisibilityLevel.level_name project_visibility}",
"wiki is #{ProjectFeature.str_from_access_level wiki_access_level}",
"user is #{user_role}"
].join(', ')
end
describe 'Situations where :read_wiki is always false' do
where(case_names: title,
project_visibility: Gitlab::VisibilityLevel.options.values,
wiki_access_level: [ProjectFeature::DISABLED],
user_role: user_roles)
with_them do
it { is_expected.to be_disallowed(:read_wiki) }
end
end
describe 'Situations where :read_wiki is always true' do
where(case_names: title,
project_visibility: [Gitlab::VisibilityLevel::PUBLIC],
wiki_access_level: [ProjectFeature::ENABLED],
user_role: user_roles)
with_them do
it { is_expected.to be_allowed(:read_wiki) }
end
end
describe 'Situations where :read_wiki requires project membership' do
context 'the wiki is private, and the user is a member' do
where(case_names: title,
project_visibility: [Gitlab::VisibilityLevel::PUBLIC,
Gitlab::VisibilityLevel::INTERNAL],
wiki_access_level: [ProjectFeature::PRIVATE],
user_role: member_roles)
with_them do
it { is_expected.to be_allowed(:read_wiki) }
end
end
context 'the wiki is private, and the user is not member' do
where(case_names: title,
project_visibility: [Gitlab::VisibilityLevel::PUBLIC,
Gitlab::VisibilityLevel::INTERNAL],
wiki_access_level: [ProjectFeature::PRIVATE],
user_role: stranger_roles)
with_them do
it { is_expected.to be_disallowed(:read_wiki) }
end
end
context 'the wiki is enabled, and the user is a member' do
where(case_names: title,
project_visibility: [Gitlab::VisibilityLevel::PRIVATE],
wiki_access_level: [ProjectFeature::ENABLED],
user_role: member_roles)
with_them do
it { is_expected.to be_allowed(:read_wiki) }
end
end
context 'the wiki is enabled, and the user is not a member' do
where(case_names: title,
project_visibility: [Gitlab::VisibilityLevel::PRIVATE],
wiki_access_level: [ProjectFeature::ENABLED],
user_role: stranger_roles)
with_them do
it { is_expected.to be_disallowed(:read_wiki) }
end
end
end
describe 'Situations where :read_wiki prohibits anonymous access' do
context 'the user is not anonymous' do
where(case_names: title,
project_visibility: [Gitlab::VisibilityLevel::INTERNAL],
wiki_access_level: [ProjectFeature::ENABLED, ProjectFeature::PUBLIC],
user_role: user_roles.reject { |u| u == :anonymous })
with_them do
it { is_expected.to be_allowed(:read_wiki) }
end
end
context 'the user is not anonymous' do
where(case_names: title,
project_visibility: [Gitlab::VisibilityLevel::INTERNAL],
wiki_access_level: [ProjectFeature::ENABLED, ProjectFeature::PUBLIC],
user_role: %i[anonymous])
with_them do
it { is_expected.to be_disallowed(:read_wiki) }
end
end
end
end end
end end
......
...@@ -14,16 +14,17 @@ RSpec.shared_context 'GroupPolicy context' do ...@@ -14,16 +14,17 @@ RSpec.shared_context 'GroupPolicy context' do
%i[ %i[
read_label read_group upload_file read_namespace read_group_activity read_label read_group upload_file read_namespace read_group_activity
read_group_issues read_group_boards read_group_labels read_group_milestones read_group_issues read_group_boards read_group_labels read_group_milestones
read_group_merge_requests read_group_merge_requests read_wiki
] ]
end end
let(:read_group_permissions) { %i[read_label read_list read_milestone read_board] } let(:read_group_permissions) { %i[read_label read_list read_milestone read_board] }
let(:reporter_permissions) { %i[admin_label read_container_image read_metrics_dashboard_annotation] } let(:reporter_permissions) { %i[admin_label read_container_image read_metrics_dashboard_annotation download_wiki_code] }
let(:developer_permissions) { %i[admin_milestone create_metrics_dashboard_annotation delete_metrics_dashboard_annotation update_metrics_dashboard_annotation] } let(:developer_permissions) { %i[admin_milestone create_metrics_dashboard_annotation delete_metrics_dashboard_annotation update_metrics_dashboard_annotation create_wiki] }
let(:maintainer_permissions) do let(:maintainer_permissions) do
%i[ %i[
create_projects create_projects
read_cluster create_cluster update_cluster admin_cluster add_cluster read_cluster create_cluster update_cluster admin_cluster add_cluster
admin_wiki
] ]
end end
let(:owner_permissions) do let(:owner_permissions) do
......
# frozen_string_literal: true
RSpec.shared_examples 'model with wiki policies' do
let(:container) { raise NotImplementedError }
let(:permissions) { %i(read_wiki create_wiki update_wiki admin_wiki download_wiki_code) }
# TODO: Remove this helper once we implement group features
# https://gitlab.com/gitlab-org/gitlab/-/issues/208412
def set_access_level(access_level)
raise NotImplementedError
end
subject { described_class.new(owner, container) }
context 'when the feature is disabled' do
before do
set_access_level(ProjectFeature::DISABLED)
end
it 'does not include the wiki permissions' do
expect_disallowed(*permissions)
end
context 'when there is an external wiki' do
it 'does not include the wiki permissions' do
allow(container).to receive(:has_external_wiki?).and_return(true)
expect_disallowed(*permissions)
end
end
end
describe 'read_wiki' do
subject { described_class.new(user, container) }
member_roles = %i[guest developer]
stranger_roles = %i[anonymous non_member]
user_roles = stranger_roles + member_roles
# When a user is anonymous, their `current_user == nil`
let(:user) { create(:user) unless user_role == :anonymous }
before do
container.visibility = container_visibility
set_access_level(wiki_access_level)
container.add_user(user, user_role) if member_roles.include?(user_role)
end
title = ->(container_visibility, wiki_access_level, user_role) do
[
"container is #{Gitlab::VisibilityLevel.level_name container_visibility}",
"wiki is #{ProjectFeature.str_from_access_level wiki_access_level}",
"user is #{user_role}"
].join(', ')
end
describe 'Situations where :read_wiki is always false' do
where(case_names: title,
container_visibility: Gitlab::VisibilityLevel.options.values,
wiki_access_level: [ProjectFeature::DISABLED],
user_role: user_roles)
with_them do
it { is_expected.to be_disallowed(:read_wiki) }
end
end
describe 'Situations where :read_wiki is always true' do
where(case_names: title,
container_visibility: [Gitlab::VisibilityLevel::PUBLIC],
wiki_access_level: [ProjectFeature::ENABLED],
user_role: user_roles)
with_them do
it { is_expected.to be_allowed(:read_wiki) }
end
end
describe 'Situations where :read_wiki requires membership' do
context 'the wiki is private, and the user is a member' do
where(case_names: title,
container_visibility: [Gitlab::VisibilityLevel::PUBLIC,
Gitlab::VisibilityLevel::INTERNAL],
wiki_access_level: [ProjectFeature::PRIVATE],
user_role: member_roles)
with_them do
it { is_expected.to be_allowed(:read_wiki) }
end
end
context 'the wiki is private, and the user is not member' do
where(case_names: title,
container_visibility: [Gitlab::VisibilityLevel::PUBLIC,
Gitlab::VisibilityLevel::INTERNAL],
wiki_access_level: [ProjectFeature::PRIVATE],
user_role: stranger_roles)
with_them do
it { is_expected.to be_disallowed(:read_wiki) }
end
end
context 'the wiki is enabled, and the user is a member' do
where(case_names: title,
container_visibility: [Gitlab::VisibilityLevel::PRIVATE],
wiki_access_level: [ProjectFeature::ENABLED],
user_role: member_roles)
with_them do
it { is_expected.to be_allowed(:read_wiki) }
end
end
context 'the wiki is enabled, and the user is not a member' do
where(case_names: title,
container_visibility: [Gitlab::VisibilityLevel::PRIVATE],
wiki_access_level: [ProjectFeature::ENABLED],
user_role: stranger_roles)
with_them do
it { is_expected.to be_disallowed(:read_wiki) }
end
end
end
describe 'Situations where :read_wiki prohibits anonymous access' do
context 'the user is not anonymous' do
where(case_names: title,
container_visibility: [Gitlab::VisibilityLevel::INTERNAL],
wiki_access_level: [ProjectFeature::ENABLED, ProjectFeature::PUBLIC],
user_role: user_roles.reject { |u| u == :anonymous })
with_them do
it { is_expected.to be_allowed(:read_wiki) }
end
end
context 'the user is anonymous' do
where(case_names: title,
container_visibility: [Gitlab::VisibilityLevel::INTERNAL],
wiki_access_level: [ProjectFeature::ENABLED, ProjectFeature::PUBLIC],
user_role: %i[anonymous])
with_them do
it { is_expected.to be_disallowed(:read_wiki) }
end
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