Commit 8a772564 authored by http://jneen.net/'s avatar http://jneen.net/

convert all the policies to DeclarativePolicy

parent d47a553b
...@@ -56,24 +56,26 @@ class Ability ...@@ -56,24 +56,26 @@ class Ability
end end
end end
def allowed?(user, action, subject = :global) def allowed?(user, action, subject = :global, opts = {})
allowed(user, subject).include?(action) if subject.is_a?(Hash)
opts, subject = subject, :global
end end
def allowed(user, subject = :global) policy = policy_for(user, subject)
return BasePolicy::RuleSet.none if subject.nil?
return uncached_allowed(user, subject) unless RequestStore.active?
user_key = user ? user.id : 'anonymous' case opts[:scope]
subject_key = subject == :global ? 'global' : "#{subject.class.name}/#{subject.id}" when :user
key = "/ability/#{user_key}/#{subject_key}" DeclarativePolicy.user_scope { policy.can?(action) }
RequestStore[key] ||= uncached_allowed(user, subject).freeze when :subject
DeclarativePolicy.subject_scope { policy.can?(action) }
else
policy.can?(action)
end
end end
private def policy_for(user, subject = :global)
cache = RequestStore.active? ? RequestStore : {}
def uncached_allowed(user, subject) DeclarativePolicy.policy_for(user, subject, cache: cache)
BasePolicy.class_for(subject).abilities(user, subject)
end end
end end
end end
...@@ -57,8 +57,11 @@ class ProjectFeature < ActiveRecord::Base ...@@ -57,8 +57,11 @@ class ProjectFeature < ActiveRecord::Base
end end
def feature_available?(feature, user) def feature_available?(feature, user)
access_level = public_send(ProjectFeature.access_level_attribute(feature)) get_permission(user, access_level(feature))
get_permission(user, access_level) end
def access_level(feature)
public_send(ProjectFeature.access_level_attribute(feature))
end end
def builds_enabled? def builds_enabled?
......
class BasePolicy require 'declarative_policy'
class RuleSet
attr_reader :can_set, :cannot_set
def initialize(can_set, cannot_set)
@can_set = can_set
@cannot_set = cannot_set
end
delegate :size, to: :to_set class BasePolicy < DeclarativePolicy::Base
desc "User is an instance admin"
with_options scope: :user, score: 0
condition(:admin) { @user&.admin? }
def self.empty with_options scope: :user, score: 0
new(Set.new, Set.new) condition(:external_user) { @user.nil? || @user.external? }
end
def self.none with_options scope: :user, score: 0
empty.freeze condition(:can_create_group) { @user&.can_create_group }
end
def can?(ability)
@can_set.include?(ability) && !@cannot_set.include?(ability)
end
def include?(ability)
can?(ability)
end
def to_set
@can_set - @cannot_set
end
def merge(other)
@can_set.merge(other.can_set)
@cannot_set.merge(other.cannot_set)
end
def can!(*abilities)
@can_set.merge(abilities)
end
def cannot!(*abilities)
@cannot_set.merge(abilities)
end
def freeze
@can_set.freeze
@cannot_set.freeze
super
end
end
def self.abilities(user, subject)
new(user, subject).abilities
end
def self.class_for(subject)
return GlobalPolicy if subject == :global
raise ArgumentError, 'no policy for nil' if subject.nil?
if subject.class.try(:presenter?)
subject = subject.subject
end
subject.class.ancestors.each do |klass|
next unless klass.name
begin
policy_class = "#{klass.name}Policy".constantize
# NOTE: the < operator here tests whether policy_class
# inherits from BasePolicy
return policy_class if policy_class < BasePolicy
rescue NameError
nil
end
end
raise "no policy for #{subject.class.name}"
end
attr_reader :user, :subject
def initialize(user, subject)
@user = user
@subject = subject
end
def abilities
return RuleSet.none if @user && @user.blocked?
return anonymous_abilities if @user.nil?
collect_rules { rules }
end
def anonymous_abilities
collect_rules { anonymous_rules }
end
def anonymous_rules
rules
end
def rules
raise NotImplementedError
end
def delegate!(new_subject)
@rule_set.merge(Ability.allowed(@user, new_subject))
end
def can?(rule)
@rule_set.can?(rule)
end
def can!(*rules)
@rule_set.can!(*rules)
end
def cannot!(*rules)
@rule_set.cannot!(*rules)
end
private
def collect_rules(&b)
@rule_set = RuleSet.empty
yield
@rule_set
end
end end
module Ci module Ci
class BuildPolicy < CommitStatusPolicy class BuildPolicy < CommitStatusPolicy
alias_method :build, :subject condition(:protected_action) do
next false unless @subject.action?
def rules
super
# If we can't read build we should also not have that
# ability when looking at this in context of commit_status
%w[read create update admin].each do |rule|
cannot! :"#{rule}_commit_status" unless can? :"#{rule}_build"
end
if can?(:update_build) && protected_action?
cannot! :update_build
end
end
private
def protected_action?
return false unless build.action?
!::Gitlab::UserAccess !::Gitlab::UserAccess
.new(user, project: build.project) .new(@user, project: @subject.project)
.can_merge_to_branch?(build.ref) .can_merge_to_branch?(@subject.ref)
end end
rule { protected_action }.prevent :update_build
end end
end end
module Ci module Ci
class PipelinePolicy < BasePolicy class PipelinePolicy < BasePolicy
def rules delegate { @subject.project }
delegate! @subject.project
end
end end
end end
module Ci module Ci
class RunnerPolicy < BasePolicy class RunnerPolicy < BasePolicy
def rules with_options scope: :subject, score: 0
return unless @user condition(:shared) { @subject.is_shared? }
can! :assign_runner if @user.admin? with_options scope: :subject, score: 0
condition(:locked, scope: :subject) { @subject.locked? }
return if @subject.is_shared? || @subject.locked? condition(:authorized_runner) { @user.ci_authorized_runners.include?(@subject) }
can! :assign_runner if @user.ci_authorized_runners.include?(@subject) rule { anonymous }.prevent_all
end rule { admin | authorized_runner }.enable :assign_runner
rule { ~admin & shared }.prevent :assign_runner
rule { ~admin & locked }.prevent :assign_runner
end end
end end
module Ci module Ci
class TriggerPolicy < BasePolicy class TriggerPolicy < BasePolicy
def rules delegate { @subject.project }
delegate! @subject.project
if can?(:admin_build) with_options scope: :subject, score: 0
can! :admin_trigger if @subject.owner.blank? || condition(:legacy) { @subject.legacy? }
@subject.owner == @user
can! :manage_trigger with_score 0
end condition(:is_owner) { @user && @subject.owner_id == @user.id }
end
rule { ~can?(:admin_build) }.prevent :admin_trigger
rule { legacy | is_owner }.enable :admin_trigger
rule { can?(:admin_build) }.enable :manage_trigger
end end
end end
class CommitStatusPolicy < BasePolicy class CommitStatusPolicy < BasePolicy
def rules delegate { @subject.project }
delegate! @subject.project
%w[read create update admin].each do |action|
rule { ~can?(:"#{action}_commit_status") }.prevent :"#{action}_build"
end end
end end
class DeployKeyPolicy < BasePolicy class DeployKeyPolicy < BasePolicy
def rules with_options scope: :subject, score: 0
return unless @user condition(:private_deploy_key) { @subject.private? }
can! :update_deploy_key if @user.admin? condition(:has_deploy_key) { @user.project_deploy_keys.exists?(id: @subject.id) }
if @subject.private? && @user.project_deploy_keys.exists?(id: @subject.id) rule { anonymous }.prevent_all
can! :update_deploy_key
end rule { admin }.enable :update_deploy_key
end rule { private_deploy_key & has_deploy_key }.enable :update_deploy_key
end end
class DeploymentPolicy < BasePolicy class DeploymentPolicy < BasePolicy
def rules delegate { @subject.project }
delegate! @subject.project
end
end end
class EnvironmentPolicy < BasePolicy class EnvironmentPolicy < BasePolicy
alias_method :environment, :subject delegate { @subject.project }
def rules condition(:stop_action_allowed) do
delegate! environment.project @subject.stop_action? && can?(:update_build, @subject.stop_action)
if can?(:create_deployment) && environment.stop_action?
can! :stop_environment if can_play_stop_action?
end
end end
private rule { can?(:create_deployment) & stop_action_allowed }.enable :stop_environment
def can_play_stop_action?
Ability.allowed?(user, :update_build, environment.stop_action)
end
end end
class ExternalIssuePolicy < BasePolicy class ExternalIssuePolicy < BasePolicy
def rules delegate { @subject.project }
delegate! @subject.project
end
end end
class GlobalPolicy < BasePolicy class GlobalPolicy < BasePolicy
def rules desc "User is blocked"
return unless @user with_options scope: :user, score: 0
condition(:blocked) { @user.blocked? }
can! :create_group if @user.can_create_group desc "User is an internal user"
can! :read_users_list with_options scope: :user, score: 0
condition(:internal) { @user.internal? }
unless @user.blocked? || @user.internal? desc "User's access has been locked"
can! :log_in unless @user.access_locked? with_options scope: :user, score: 0
can! :access_api condition(:access_locked) { @user.access_locked? }
can! :access_git
can! :receive_notifications rule { anonymous }.prevent_all
can! :use_quick_actions
rule { default }.policy do
enable :read_users_list
enable :log_in
enable :access_api
enable :access_git
enable :receive_notifications
enable :use_quick_actions
end
rule { blocked | internal }.policy do
prevent :log_in
prevent :access_api
prevent :access_git
prevent :receive_notifications
prevent :use_quick_actions
end end
rule { can_create_group }.policy do
enable :create_group
end
rule { access_locked }.policy do
prevent :log_in
end end
end end
class GroupLabelPolicy < BasePolicy class GroupLabelPolicy < BasePolicy
def rules delegate { @subject.group }
delegate! @subject.group
end
end end
class GroupMemberPolicy < BasePolicy class GroupMemberPolicy < BasePolicy
def rules delegate :group
return unless @user
target_user = @subject.user with_scope :subject
group = @subject.group condition(:last_owner) { @subject.group.last_owner?(@subject.user) }
return if group.last_owner?(target_user) desc "Membership is users' own"
with_score 0
condition(:is_target_user) { @user && @subject.user_id == @user.id }
can_manage = Ability.allowed?(@user, :admin_group_member, group) rule { anonymous }.prevent_all
rule { last_owner }.prevent_all
if can_manage rule { can?(:admin_group_member) }.policy do
can! :update_group_member enable :update_group_member
can! :destroy_group_member enable :destroy_group_member
elsif @user == target_user
can! :destroy_group_member
end end
additional_rules! rule { is_target_user }.policy do
end enable :destroy_group_member
def additional_rules!
can_override = Ability.allowed?(@user, :override_group_member, @subject.group)
if can_override
can! :override_group_member if @subject.ldap?
can! :update_group_member unless @subject.ldap? && !@subject.override?
end
end end
end end
class GroupPolicy < BasePolicy class GroupPolicy < BasePolicy
prepend EE::GroupPolicy desc "Group is public"
with_options scope: :subject, score: 0
def rules condition(:public_group) { @subject.public? }
can! :read_group if @subject.public?
return unless @user
globally_viewable = @subject.public? || (@subject.internal? && !@user.external?)
access_level = @subject.max_member_access_for_user(@user)
owner = access_level >= GroupMember::OWNER
master = access_level >= GroupMember::MASTER
reporter = access_level >= GroupMember::REPORTER
can_read = false
can_read ||= globally_viewable
can_read ||= @user.admin?
can_read ||= @user.auditor?
can_read ||= access_level >= GroupMember::GUEST
can_read ||= GroupProjectsFinder.new(group: @subject, current_user: @user).execute.any?
can! :read_group if can_read
if reporter
can! :admin_label
end
# Only group masters and group owners can create new projects with_score 0
if master condition(:logged_in_viewable) { @user && @subject.internal? && !@user.external? }
can! :create_projects
can! :admin_milestones condition(:has_access) { access_level != GroupMember::NO_ACCESS }
end
condition(:guest) { access_level >= GroupMember::GUEST }
condition(:owner) { access_level >= GroupMember::OWNER }
condition(:master) { access_level >= GroupMember::MASTER }
condition(:reporter) { access_level >= GroupMember::REPORTER }
# Only group owner and administrators can admin group condition(:has_projects) do
if owner GroupProjectsFinder.new(group: @subject, current_user: @user).execute.any?
can! :admin_group
can! :admin_namespace
can! :admin_group_member
can! :change_visibility_level
can! :create_subgroup if @user.can_create_group
end end
if globally_viewable && @subject.request_access_enabled && access_level == GroupMember::NO_ACCESS with_options scope: :subject, score: 0
can! :request_access condition(:request_access_enabled) { @subject.request_access_enabled }
rule { public_group } .enable :read_group
rule { logged_in_viewable }.enable :read_group
rule { guest } .enable :read_group
rule { admin } .enable :read_group
rule { has_projects } .enable :read_group
rule { reporter }.enable :admin_label
rule { master }.policy do
enable :create_projects
enable :admin_milestones
end end
rule { owner }.policy do
enable :admin_group
enable :admin_namespace
enable :admin_group_member
enable :change_visibility_level
end end
def can_read_group? rule { owner & can_create_group }.enable :create_subgroup
return true if @subject.public?
return true if @user.admin?
return true if @user.auditor?
return true if @subject.internal? && !@user.external?
return true if @subject.users.include?(@user)
GroupProjectsFinder.new(group: @subject, current_user: @user).execute.any? rule { public_group | logged_in_viewable }.enable :view_globally
rule { default }.enable(:request_access)
rule { ~request_access_enabled }.prevent :request_access
rule { ~can?(:view_globally) }.prevent :request_access
rule { has_access }.prevent :request_access
def access_level
return GroupMember::NO_ACCESS if @user.nil?
@access_level ||= @subject.max_member_access_for_user(@user)
end end
end end
class IssuablePolicy < BasePolicy class IssuablePolicy < BasePolicy
def action_name delegate { @subject.project }
@subject.class.name.underscore
end
def rules desc "User is the assignee or author"
if @user && @subject.assignee_or_author?(@user) condition(:assignee_or_author) do
can! :"read_#{action_name}" @user && @subject.assignee_or_author?(@user)
can! :"update_#{action_name}"
end end
delegate! @subject.project rule { assignee_or_author }.policy do
enable :read_issue
enable :update_issue
enable :read_merge_request
enable :update_merge_request
end end
end end
...@@ -3,25 +3,17 @@ class IssuePolicy < IssuablePolicy ...@@ -3,25 +3,17 @@ 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.
def issue desc "User can read confidential issues"
@subject condition(:can_read_confidential) do
@user && IssueCollection.new([@subject]).visible_to(@user).any?
end end
def rules desc "Issue is confidential"
super condition(:confidential, scope: :subject) { @subject.confidential? }
if @subject.confidential? && !can_read_confidential? rule { confidential & ~can_read_confidential }.policy do
cannot! :read_issue prevent :read_issue
cannot! :update_issue prevent :update_issue
cannot! :admin_issue prevent :admin_issue
end
end
private
def can_read_confidential?
return false unless @user
IssueCollection.new([@subject]).visible_to(@user).any?
end end
end end
class NamespacePolicy < BasePolicy class NamespacePolicy < BasePolicy
def rules rule { anonymous }.prevent_all
return unless @user
if @subject.owner == @user || @user.admin? condition(:owner) { @subject.owner == @user }
can! :create_projects
can! :admin_namespace rule { owner | admin }.policy do
end enable :create_projects
enable :admin_namespace
end end
end end
class NilPolicy < BasePolicy
rule { default }.prevent_all
end
class NotePolicy < BasePolicy class NotePolicy < BasePolicy
def rules delegate { @subject.project }
delegate! @subject.project
return unless @user condition(:is_author) { @user && @subject.author == @user }
condition(:for_merge_request, scope: :subject) { @subject.for_merge_request? }
condition(:is_noteable_author) { @user && @subject.noteable.author_id == @user.id }
if @subject.author == @user condition(:editable, scope: :subject) { @subject.editable? }
can! :read_note
can! :update_note rule { ~editable | anonymous }.prevent :edit_note
can! :admin_note rule { is_author | admin }.enable :edit_note
can! :resolve_note rule { can?(:master_access) }.enable :edit_note
end
if @subject.for_merge_request? && rule { is_author }.policy do
@subject.noteable.author == @user enable :read_note
can! :resolve_note enable :update_note
enable :admin_note
enable :resolve_note
end end
rule { for_merge_request & is_noteable_author }.policy do
enable :resolve_note
end end
end end
class PersonalSnippetPolicy < BasePolicy class PersonalSnippetPolicy < BasePolicy
def rules condition(:public_snippet, scope: :subject) { @subject.public? }
can! :read_personal_snippet if @subject.public? condition(:is_author) { @user && @subject.author == @user }
return unless @user condition(:internal_snippet, scope: :subject) { @subject.internal? }
if @subject.public? rule { public_snippet }.policy do
can! :comment_personal_snippet enable :read_personal_snippet
enable :comment_personal_snippet
end end
if @subject.author == @user rule { is_author }.policy do
can! :read_personal_snippet enable :read_personal_snippet
can! :update_personal_snippet enable :update_personal_snippet
can! :destroy_personal_snippet enable :destroy_personal_snippet
can! :admin_personal_snippet enable :admin_personal_snippet
can! :comment_personal_snippet enable :comment_personal_snippet
end end
unless @user.external? rule { ~anonymous }.enable :create_personal_snippet
can! :create_personal_snippet rule { external_user }.prevent :create_personal_snippet
end
if @subject.internal? && !@user.external? rule { internal_snippet & ~external_user }.policy do
can! :read_personal_snippet enable :read_personal_snippet
can! :comment_personal_snippet enable :comment_personal_snippet
end
end end
rule { anonymous }.prevent :comment_personal_snippet
end end
class ProjectLabelPolicy < BasePolicy class ProjectLabelPolicy < BasePolicy
def rules delegate { @subject.project }
delegate! @subject.project
end
end end
class ProjectMemberPolicy < BasePolicy class ProjectMemberPolicy < BasePolicy
def rules delegate { @subject.project }
# anonymous users have no abilities here
return unless @user
target_user = @subject.user condition(:target_is_owner, scope: :subject) { @subject.user == @subject.project.owner }
project = @subject.project condition(:target_is_self) { @user && @subject.user == @user }
return if target_user == project.owner rule { anonymous }.prevent_all
rule { target_is_owner }.prevent_all
can_manage = Ability.allowed?(@user, :admin_project_member, project) rule { can?(:admin_project_member) }.policy do
enable :update_project_member
if can_manage enable :destroy_project_member
can! :update_project_member
can! :destroy_project_member
end end
if @user == target_user rule { target_is_self }.enable :destroy_project_member
can! :destroy_project_member
end
end
end end
class ProjectPolicy < BasePolicy class ProjectPolicy < BasePolicy
prepend EE::ProjectPolicy def self.create_read_update_admin(name)
[
def rules :"create_#{name}",
team_access!(user) :"read_#{name}",
:"update_#{name}",
owner_access! if user.admin? || owner? :"admin_#{name}"
auditor_access! if user.auditor? ]
team_member_owner_access! if owner?
if project.public? || (project.internal? && !user.external?)
guest_access!
public_access!
can! :request_access if access_requestable?
end
archived_access! if project.archived?
# EE-only
can! :change_repository_storage if user.admin?
disabled_features!
end end
def project desc "User is a project owner"
@subject condition :owner do
@user && project.owner == @user || (project.group && project.group.has_owner?(@user))
end end
def owner? desc "Project has public builds enabled"
return @owner if defined?(@owner) condition(:public_builds, scope: :subject) { project.public_builds? }
@owner = project.owner == user ||
(project.group && project.group.has_owner?(user))
end
def guest_access!
can! :read_project
can! :read_board
can! :read_list
can! :read_wiki
can! :read_issue
can! :read_label
can! :read_milestone
can! :read_project_snippet
can! :read_project_member
can! :read_note
can! :create_project
can! :create_issue
can! :create_note
can! :upload_file
can! :read_cycle_analytics
if project.public_builds?
can! :read_pipeline
can! :read_pipeline_schedule
can! :read_build
end
# EE-only
can! :read_issue_link
end
def reporter_access!
can! :download_code
can! :download_wiki_code
can! :fork_project
can! :create_project_snippet
can! :update_issue
can! :admin_issue
can! :admin_label
can! :admin_board
can! :admin_list
can! :read_commit_status
can! :read_build
can! :read_container_image
can! :read_pipeline
can! :read_pipeline_schedule
can! :read_environment
can! :read_deployment
can! :read_merge_request
if project.feature_available?(:deploy_board) || Rails.env.development?
can! :read_deploy_board
end
# EE-only
can! :admin_issue_link
end
# Permissions given when an user is team member of a project
def team_member_reporter_access!
can! :build_download_code
can! :build_read_container_image
end
def developer_access!
can! :admin_merge_request
can! :update_merge_request
can! :create_commit_status
can! :update_commit_status
can! :create_build
can! :update_build
can! :create_pipeline
can! :update_pipeline
can! :create_pipeline_schedule
can! :update_pipeline_schedule
can! :create_merge_request
can! :create_wiki
can! :push_code
can! :resolve_note
can! :create_container_image
can! :update_container_image
can! :create_environment
can! :create_deployment
can! :admin_board
end
def master_access!
can! :push_code_to_protected_branches
can! :delete_protected_branch
can! :update_project_snippet
can! :update_environment
can! :update_deployment
can! :admin_milestone
can! :admin_project_snippet
can! :admin_project_member
can! :admin_note
can! :admin_wiki
can! :admin_project
can! :admin_commit_status
can! :admin_build
can! :admin_container_image
can! :admin_pipeline
can! :admin_pipeline_schedule
can! :admin_environment
can! :admin_deployment
can! :admin_pages
can! :read_pages
can! :update_pages
# EE-only
can! :admin_path_locks
end
def public_access!
can! :download_code
can! :fork_project
can! :read_commit_status
can! :read_pipeline
can! :read_pipeline_schedule
can! :read_container_image
can! :build_download_code
can! :build_read_container_image
can! :read_merge_request
end
def owner_access!
guest_access!
reporter_access!
developer_access!
master_access!
can! :change_namespace
can! :change_visibility_level
can! :rename_project
can! :remove_project
can! :archive_project
can! :remove_fork_project
can! :destroy_merge_request
can! :destroy_issue
can! :remove_pages
end
def team_member_owner_access! # For guest access we use #is_team_member? so we can use
team_member_reporter_access! # project.members, which gets cached in subject scope.
end # This is safe because team_access_level is guaranteed
# by ProjectAuthorization's validation to be at minimum
# GUEST
desc "User has guest access"
condition(:guest) { is_team_member? }
# Push abilities on the users team role desc "User has reporter access"
def team_access!(user) condition(:reporter) { team_access_level >= Gitlab::Access::REPORTER }
access = project.team.max_member_access(user.id)
return if access < Gitlab::Access::GUEST desc "User has developer access"
guest_access! condition(:developer) { team_access_level >= Gitlab::Access::DEVELOPER }
return if access < Gitlab::Access::REPORTER desc "User has master access"
reporter_access! condition(:master) { team_access_level >= Gitlab::Access::MASTER }
team_member_reporter_access!
return if access < Gitlab::Access::DEVELOPER desc "Project is public"
developer_access! condition(:public_project, scope: :subject) { project.public? }
return if access < Gitlab::Access::MASTER desc "Project is visible to internal users"
master_access! condition(:internal_access) do
project.internal? && !user.external?
end end
def archived_access! desc "User is a member of the group"
cannot! :create_merge_request condition(:group_member, scope: :subject) { project_group_member? }
cannot! :push_code
cannot! :push_code_to_protected_branches desc "Project is archived"
cannot! :delete_protected_branch condition(:archived, scope: :subject) { project.archived? }
cannot! :update_merge_request
cannot! :admin_merge_request
end
# An auditor user has read-only access to all projects condition(:default_issues_tracker, scope: :subject) { project.default_issues_tracker? }
def auditor_access!
base_readonly_access!
can! :read_build desc "Container registry is disabled"
can! :read_environment condition(:container_registry_disabled, scope: :subject) do
can! :read_deployment !project.container_registry_enabled
can! :read_pages
end end
def disabled_features! desc "Project has an external wiki"
repository_enabled = project.feature_available?(:repository, user) condition(:has_external_wiki, scope: :subject) { project.has_external_wiki? }
block_issues_abilities desc "Project has request access enabled"
condition(:request_access_enabled, scope: :subject) { project.request_access_enabled }
unless project.feature_available?(:merge_requests, user) && repository_enabled features = %w[
cannot!(*named_abilities(:merge_request)) merge_requests
end issues
repository
snippets
wiki
builds
]
unless project.feature_available?(:issues, user) || project.feature_available?(:merge_requests, user) features.each do |f|
cannot!(*named_abilities(:label)) # these are scored high because they are unlikely
cannot!(*named_abilities(:milestone)) desc "Project has #{f} disabled"
end condition(:"#{f}_disabled", score: 32) { !feature_available?(f.to_sym) }
end
rule { guest }.enable :guest_access
rule { reporter }.enable :reporter_access
rule { developer }.enable :developer_access
rule { master }.enable :master_access
rule { owner | admin }.policy do
enable :guest_access
enable :reporter_access
enable :developer_access
enable :master_access
enable :change_namespace
enable :change_visibility_level
enable :rename_project
enable :remove_project
enable :archive_project
enable :remove_fork_project
enable :destroy_merge_request
enable :destroy_issue
enable :remove_pages
end
rule { owner | reporter }.policy do
enable :build_download_code
enable :build_read_container_image
end
rule { can?(:guest_access) }.policy do
enable :read_project
enable :read_board
enable :read_list
enable :read_wiki
enable :read_issue
enable :read_label
enable :read_milestone
enable :read_project_snippet
enable :read_project_member
enable :read_note
enable :create_project
enable :create_issue
enable :create_note
enable :upload_file
enable :read_cycle_analytics
enable :read_project_snippet
end
rule { can?(:reporter_access) }.policy do
enable :download_code
enable :download_wiki_code
enable :fork_project
enable :create_project_snippet
enable :update_issue
enable :admin_issue
enable :admin_label
enable :admin_list
enable :read_commit_status
enable :read_build
enable :read_container_image
enable :read_pipeline
enable :read_pipeline_schedule
enable :read_environment
enable :read_deployment
enable :read_merge_request
end
rule { (~anonymous & public_project) | internal_access }.policy do
enable :public_user_access
end
rule { can?(:public_user_access) }.policy do
enable :guest_access
enable :request_access
end
rule { owner | admin | guest | group_member }.prevent :request_access
rule { ~request_access_enabled }.prevent :request_access
rule { can?(:developer_access) }.policy do
enable :admin_merge_request
enable :update_merge_request
enable :create_commit_status
enable :update_commit_status
enable :create_build
enable :update_build
enable :create_pipeline
enable :update_pipeline
enable :create_pipeline_schedule
enable :update_pipeline_schedule
enable :create_merge_request
enable :create_wiki
enable :push_code
enable :resolve_note
enable :create_container_image
enable :update_container_image
enable :create_environment
enable :create_deployment
end
rule { can?(:master_access) }.policy do
enable :delete_protected_branch
enable :update_project_snippet
enable :update_environment
enable :update_deployment
enable :admin_milestone
enable :admin_project_snippet
enable :admin_project_member
enable :admin_note
enable :admin_wiki
enable :admin_project
enable :admin_commit_status
enable :admin_build
enable :admin_container_image
enable :admin_pipeline
enable :admin_pipeline_schedule
enable :admin_environment
enable :admin_deployment
enable :admin_pages
enable :read_pages
enable :update_pages
end
rule { can?(:public_user_access) }.policy do
enable :public_access
enable :fork_project
enable :build_download_code
enable :build_read_container_image
end
rule { archived }.policy do
prevent :create_merge_request
prevent :push_code
prevent :delete_protected_branch
prevent :update_merge_request
prevent :admin_merge_request
end
rule { merge_requests_disabled | repository_disabled }.policy do
prevent(*create_read_update_admin(:merge_request))
end
rule { issues_disabled & merge_requests_disabled }.policy do
prevent(*create_read_update_admin(:label))
prevent(*create_read_update_admin(:milestone))
end
rule { snippets_disabled }.policy do
prevent(*create_read_update_admin(:project_snippet))
end
rule { wiki_disabled & ~has_external_wiki }.policy do
prevent(*create_read_update_admin(:wiki))
prevent(:download_wiki_code)
end
rule { builds_disabled | repository_disabled }.policy do
prevent(*create_read_update_admin(:build))
prevent(*(create_read_update_admin(:pipeline) - [:read_pipeline]))
prevent(*create_read_update_admin(:pipeline_schedule))
prevent(*create_read_update_admin(:environment))
prevent(*create_read_update_admin(:deployment))
end
rule { repository_disabled }.policy do
prevent :push_code
prevent :push_code_to_protected_branches
prevent :download_code
prevent :fork_project
prevent :read_commit_status
end
rule { container_registry_disabled }.policy do
prevent(*create_read_update_admin(:container_image))
end
rule { anonymous & ~public_project }.prevent_all
rule { public_project }.enable(:public_access)
rule { can?(:public_access) }.policy do
enable :read_project
enable :read_board
enable :read_list
enable :read_wiki
enable :read_label
enable :read_milestone
enable :read_project_snippet
enable :read_project_member
enable :read_merge_request
enable :read_note
enable :read_pipeline
enable :read_pipeline_schedule
enable :read_commit_status
enable :read_container_image
enable :download_code
enable :download_wiki_code
enable :read_cycle_analytics
unless project.feature_available?(:snippets, user) # NOTE: may be overridden by IssuePolicy
cannot!(*named_abilities(:project_snippet)) enable :read_issue
end end
unless project.feature_available?(:wiki, user) || project.has_external_wiki? rule { public_builds }.policy do
cannot!(*named_abilities(:wiki)) enable :read_build
cannot!(:download_wiki_code)
end end
unless project.feature_available?(:builds, user) && repository_enabled rule { public_builds & can?(:guest_access) }.policy do
cannot!(*named_abilities(:build)) enable :read_pipeline
cannot!(*named_abilities(:pipeline) - [:read_pipeline]) enable :read_pipeline_schedule
cannot!(*named_abilities(:pipeline_schedule))
cannot!(*named_abilities(:environment))
cannot!(*named_abilities(:deployment))
end end
unless repository_enabled rule { issues_disabled }.policy do
cannot! :push_code prevent :create_issue
cannot! :push_code_to_protected_branches prevent :update_issue
cannot! :delete_protected_branch prevent :admin_issue
cannot! :download_code
cannot! :fork_project
cannot! :read_commit_status
end end
unless project.container_registry_enabled rule { issues_disabled & default_issues_tracker }.policy do
cannot!(*named_abilities(:container_image)) prevent :read_issue
end
end end
def anonymous_rules private
return unless project.public?
base_readonly_access! def is_team_member?
return false if @user.nil?
# Allow to read builds by anonymous user if guests are allowed greedy_load_subject = false
can! :read_build if project.public_builds?
disabled_features! # when scoping by subject, we want to be greedy
end # and load *all* the members with one query.
greedy_load_subject ||= DeclarativePolicy.preferred_scope == :subject
def block_issues_abilities # in this case we're likely to have loaded #members already
unless project.feature_available?(:issues, user) # anyways, and #member? would fail with an error
cannot! :read_issue if project.default_issues_tracker? greedy_load_subject ||= !@user.persisted?
cannot! :create_issue
cannot! :update_issue
cannot! :admin_issue
end
end
def named_abilities(name) if greedy_load_subject
[ project.team.members.include?(user)
:"read_#{name}", else
:"create_#{name}", # otherwise we just make a specific query for
:"update_#{name}", # this particular user.
:"admin_#{name}" team_access_level >= Gitlab::Access::GUEST
] end
end end
private def project_group_member?
return false if @user.nil?
def project_group_member?(user)
project.group && project.group &&
( (
project.group.members_with_parents.exists?(user_id: user.id) || project.group.members_with_parents.exists?(user_id: @user.id) ||
project.group.requesters.exists?(user_id: user.id) project.group.requesters.exists?(user_id: @user.id)
) )
end end
def access_requestable? def team_access_level
project.request_access_enabled && return -1 if @user.nil?
!owner? &&
!user.admin? &&
!project.team.member?(user) &&
!project_group_member?(user)
end
# A base set of abilities for read-only users, which
# is then augmented as necessary for anonymous and other
# read-only users.
def base_readonly_access!
can! :read_project
can! :read_board
can! :read_list
can! :read_wiki
can! :read_label
can! :read_milestone
can! :read_project_snippet
can! :read_project_member
can! :read_merge_request
can! :read_note
can! :read_pipeline
can! :read_pipeline_schedule
can! :read_commit_status
can! :read_container_image
can! :download_code
can! :download_wiki_code
can! :read_cycle_analytics
# NOTE: may be overridden by IssuePolicy # NOTE: max_member_access has its own cache
can! :read_issue project.team.max_member_access(@user.id)
end
# EE-only def feature_available?(feature)
can! :read_issue_link case project.project_feature.access_level(feature)
when ProjectFeature::DISABLED
false
when ProjectFeature::PRIVATE
guest? || admin?
else
true
end
end
def project
@subject
end end
end end
class ProjectSnippetPolicy < BasePolicy class ProjectSnippetPolicy < BasePolicy
def rules delegate :project
# We have to check both project feature visibility and a snippet visibility and take the stricter one
# This will be simplified - check https://gitlab.com/gitlab-org/gitlab-ce/issues/27573
return unless @subject.project.feature_available?(:snippets, @user)
return unless Ability.allowed?(@user, :read_project, @subject.project)
can! :read_project_snippet if @subject.public? desc "Snippet is public"
return unless @user condition(:public_snippet, scope: :subject) { @subject.public? }
condition(:private_snippet, scope: :subject) { @subject.private? }
condition(:public_project, scope: :subject) { @subject.project.public? }
if @user && (@subject.author == @user || @user.admin?) condition(:is_author) { @user && @subject.author == @user }
can! :read_project_snippet
can! :update_project_snippet
can! :admin_project_snippet
end
if @user.auditor? condition(:internal, scope: :subject) { @subject.internal? }
can! :read_project_snippet
end
if @subject.internal? && !@user.external? # We have to check both project feature visibility and a snippet visibility and take the stricter one
can! :read_project_snippet # This will be simplified - check https://gitlab.com/gitlab-org/gitlab-ce/issues/27573
rule { ~can?(:read_project) }.policy do
prevent :read_project_snippet
prevent :update_project_snippet
prevent :admin_project_snippet
end end
if @subject.project.team.member?(@user) # we have to use this complicated prevent because the delegated project policy
can! :read_project_snippet # is overly greedy in allowing :read_project_snippet, since it doesn't have any
# information about the snippet. However, :read_project_snippet on the *project*
# is used to hide/show various snippet-related controls, so we can't just move
# all of the handling here.
rule do
all?(private_snippet | (internal & external_user),
~project.guest,
~admin,
~is_author)
end.prevent :read_project_snippet
rule { internal & ~is_author & ~admin }.policy do
prevent :update_project_snippet
prevent :admin_project_snippet
end end
rule { public_snippet }.enable :read_project_snippet
rule { is_author | admin }.policy do
enable :read_project_snippet
enable :update_project_snippet
enable :admin_project_snippet
end end
end end
class UserPolicy < BasePolicy class UserPolicy < BasePolicy
include Gitlab::CurrentSettings include Gitlab::CurrentSettings
def rules desc "The application is restricted from public visibility"
can! :read_user if @user || !restricted_public_level? condition(:restricted_public_level, scope: :global) do
current_application_settings.restricted_visibility_levels.include?(Gitlab::VisibilityLevel::PUBLIC)
if @user
if @user.admin? || @subject == @user
can! :destroy_user
end end
cannot! :destroy_user if @subject.ghost? desc "The current user is the user in question"
end condition(:user_is_self, score: 0) { @subject == @user }
end
def restricted_public_level? desc "This is the ghost user"
current_application_settings.restricted_visibility_levels.include?(Gitlab::VisibilityLevel::PUBLIC) condition(:subject_ghost, scope: :subject, score: 0) { @subject.ghost? }
end
rule { ~restricted_public_level }.enable :read_user
rule { ~anonymous }.enable :read_user
rule { user_is_self | admin }.enable :destroy_user
rule { subject_ghost }.prevent :destroy_user
end end
module Gitlab module Gitlab
module Allowable module Allowable
def can?(user, action, subject = :global) def can?(*args)
Ability.allowed?(user, action, subject) Ability.allowed?(*args)
end end
end end
end end
...@@ -167,8 +167,8 @@ describe ProjectPolicy, models: true do ...@@ -167,8 +167,8 @@ describe ProjectPolicy, models: true do
end end
it do it do
is_expected.not_to include(:read_build) expect_disallowed(:read_build)
is_expected.to include(:read_pipeline) expect_allowed(:read_pipeline)
end end
end end
end end
......
...@@ -129,7 +129,7 @@ describe ProjectSnippetPolicy, models: true do ...@@ -129,7 +129,7 @@ describe ProjectSnippetPolicy, models: true do
context 'snippet author' do context 'snippet author' do
let(:snippet) { create(:project_snippet, :private, author: regular_user, project: project) } let(:snippet) { create(:project_snippet, :private, author: regular_user, project: project) }
subject { described_class(regular_user, snippet) } subject { described_class.new(regular_user, snippet) }
it do it do
expect_allowed(:read_project_snippet) expect_allowed(:read_project_snippet)
......
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