Commit 37c40143 authored by http://jneen.net/'s avatar http://jneen.net/

convert all the policies to DeclarativePolicy

parent e5aad75a
......@@ -56,24 +56,26 @@ class Ability
end
end
def allowed?(user, action, subject = :global)
allowed(user, subject).include?(action)
end
def allowed?(user, action, subject = :global, opts = {})
if subject.is_a?(Hash)
opts, subject = subject, :global
end
def allowed(user, subject = :global)
return BasePolicy::RuleSet.none if subject.nil?
return uncached_allowed(user, subject) unless RequestStore.active?
policy = policy_for(user, subject)
user_key = user ? user.id : 'anonymous'
subject_key = subject == :global ? 'global' : "#{subject.class.name}/#{subject.id}"
key = "/ability/#{user_key}/#{subject_key}"
RequestStore[key] ||= uncached_allowed(user, subject).freeze
case opts[:scope]
when :user
DeclarativePolicy.user_scope { policy.can?(action) }
when :subject
DeclarativePolicy.subject_scope { policy.can?(action) }
else
policy.can?(action)
end
end
private
def uncached_allowed(user, subject)
BasePolicy.class_for(subject).abilities(user, subject)
def policy_for(user, subject = :global)
cache = RequestStore.active? ? RequestStore : {}
DeclarativePolicy.policy_for(user, subject, cache: cache)
end
end
end
......@@ -51,8 +51,11 @@ class ProjectFeature < ActiveRecord::Base
default_value_for :repository_access_level, value: ENABLED, allows_nil: false
def feature_available?(feature, user)
access_level = public_send(ProjectFeature.access_level_attribute(feature))
get_permission(user, access_level)
get_permission(user, access_level(feature))
end
def access_level(feature)
public_send(ProjectFeature.access_level_attribute(feature))
end
def builds_enabled?
......
class BasePolicy
class RuleSet
attr_reader :can_set, :cannot_set
def initialize(can_set, cannot_set)
@can_set = can_set
@cannot_set = cannot_set
end
require 'declarative_policy'
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
new(Set.new, Set.new)
end
with_options scope: :user, score: 0
condition(:external_user) { @user.nil? || @user.external? }
def self.none
empty.freeze
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
with_options scope: :user, score: 0
condition(:can_create_group) { @user&.can_create_group }
end
module Ci
class BuildPolicy < CommitStatusPolicy
alias_method :build, :subject
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?
condition(:protected_action) do
next false unless @subject.action?
!::Gitlab::UserAccess
.new(user, project: build.project)
.can_merge_to_branch?(build.ref)
.new(@user, project: @subject.project)
.can_merge_to_branch?(@subject.ref)
end
rule { protected_action }.prevent :update_build
end
end
module Ci
class PipelinePolicy < BasePolicy
def rules
delegate! @subject.project
end
delegate { @subject.project }
end
end
module Ci
class RunnerPolicy < BasePolicy
def rules
return unless @user
with_options scope: :subject, score: 0
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)
end
rule { anonymous }.prevent_all
rule { admin | authorized_runner }.enable :assign_runner
rule { ~admin & shared }.prevent :assign_runner
rule { ~admin & locked }.prevent :assign_runner
end
end
module Ci
class TriggerPolicy < BasePolicy
def rules
delegate! @subject.project
if can?(:admin_build)
can! :admin_trigger if @subject.owner.blank? ||
@subject.owner == @user
can! :manage_trigger
end
end
delegate { @subject.project }
with_options scope: :subject, score: 0
condition(:legacy) { @subject.legacy? }
with_score 0
condition(:is_owner) { @user && @subject.owner_id == @user.id }
rule { ~can?(:admin_build) }.prevent :admin_trigger
rule { legacy | is_owner }.enable :admin_trigger
rule { can?(:admin_build) }.enable :manage_trigger
end
end
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
class DeployKeyPolicy < BasePolicy
def rules
return unless @user
with_options scope: :subject, score: 0
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)
can! :update_deploy_key
end
end
rule { anonymous }.prevent_all
rule { admin }.enable :update_deploy_key
rule { private_deploy_key & has_deploy_key }.enable :update_deploy_key
end
class DeploymentPolicy < BasePolicy
def rules
delegate! @subject.project
end
delegate { @subject.project }
end
class EnvironmentPolicy < BasePolicy
alias_method :environment, :subject
delegate { @subject.project }
def rules
delegate! environment.project
if can?(:create_deployment) && environment.stop_action?
can! :stop_environment if can_play_stop_action?
end
condition(:stop_action_allowed) do
@subject.stop_action? && can?(:update_build, @subject.stop_action)
end
private
def can_play_stop_action?
Ability.allowed?(user, :update_build, environment.stop_action)
end
rule { can?(:create_deployment) & stop_action_allowed }.enable :stop_environment
end
class ExternalIssuePolicy < BasePolicy
def rules
delegate! @subject.project
end
delegate { @subject.project }
end
class GlobalPolicy < BasePolicy
def rules
return unless @user
desc "User is blocked"
with_options scope: :user, score: 0
condition(:blocked) { @user.blocked? }
can! :create_group if @user.can_create_group
can! :read_users_list
desc "User is an internal user"
with_options scope: :user, score: 0
condition(:internal) { @user.internal? }
unless @user.blocked? || @user.internal?
can! :log_in unless @user.access_locked?
can! :access_api
can! :access_git
can! :receive_notifications
can! :use_quick_actions
end
desc "User's access has been locked"
with_options scope: :user, score: 0
condition(:access_locked) { @user.access_locked? }
rule { anonymous }.prevent_all
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
rule { can_create_group }.policy do
enable :create_group
end
rule { access_locked }.policy do
prevent :log_in
end
end
class GroupLabelPolicy < BasePolicy
def rules
delegate! @subject.group
end
delegate { @subject.group }
end
class GroupMemberPolicy < BasePolicy
def rules
return unless @user
delegate :group
target_user = @subject.user
group = @subject.group
with_scope :subject
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
can! :update_group_member
can! :destroy_group_member
elsif @user == target_user
can! :destroy_group_member
end
additional_rules!
rule { can?(:admin_group_member) }.policy do
enable :update_group_member
enable :destroy_group_member
end
def additional_rules!
# This is meant to be overriden in EE
rule { is_target_user }.policy do
enable :destroy_group_member
end
end
class GroupPolicy < BasePolicy
def rules
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 ||= 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
if master
can! :create_projects
can! :admin_milestones
end
# Only group owner and administrators can admin group
if owner
can! :admin_group
can! :admin_namespace
can! :admin_group_member
can! :change_visibility_level
can! :create_subgroup if @user.can_create_group
end
if globally_viewable && @subject.request_access_enabled && access_level == GroupMember::NO_ACCESS
can! :request_access
end
end
desc "Group is public"
with_options scope: :subject, score: 0
condition(:public_group) { @subject.public? }
with_score 0
condition(:logged_in_viewable) { @user && @subject.internal? && !@user.external? }
condition(:has_access) { access_level != GroupMember::NO_ACCESS }
def can_read_group?
return true if @subject.public?
return true if @user.admin?
return true if @subject.internal? && !@user.external?
return true if @subject.users.include?(@user)
condition(:guest) { access_level >= GroupMember::GUEST }
condition(:owner) { access_level >= GroupMember::OWNER }
condition(:master) { access_level >= GroupMember::MASTER }
condition(:reporter) { access_level >= GroupMember::REPORTER }
condition(:has_projects) do
GroupProjectsFinder.new(group: @subject, current_user: @user).execute.any?
end
with_options scope: :subject, score: 0
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
rule { owner }.policy do
enable :admin_group
enable :admin_namespace
enable :admin_group_member
enable :change_visibility_level
end
rule { owner & can_create_group }.enable :create_subgroup
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
class IssuablePolicy < BasePolicy
def action_name
@subject.class.name.underscore
end
delegate { @subject.project }
def rules
if @user && @subject.assignee_or_author?(@user)
can! :"read_#{action_name}"
can! :"update_#{action_name}"
end
desc "User is the assignee or author"
condition(:assignee_or_author) do
@user && @subject.assignee_or_author?(@user)
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
......@@ -3,25 +3,17 @@ class IssuePolicy < IssuablePolicy
# Make sure to sync this class checks with issue.rb to avoid security problems.
# Check commit 002ad215818450d2cbbc5fa065850a953dc7ada8 for more information.
def issue
@subject
desc "User can read confidential issues"
condition(:can_read_confidential) do
@user && IssueCollection.new([@subject]).visible_to(@user).any?
end
def rules
super
desc "Issue is confidential"
condition(:confidential, scope: :subject) { @subject.confidential? }
if @subject.confidential? && !can_read_confidential?
cannot! :read_issue
cannot! :update_issue
cannot! :admin_issue
end
end
private
def can_read_confidential?
return false unless @user
IssueCollection.new([@subject]).visible_to(@user).any?
rule { confidential & ~can_read_confidential }.policy do
prevent :read_issue
prevent :update_issue
prevent :admin_issue
end
end
class NamespacePolicy < BasePolicy
def rules
return unless @user
rule { anonymous }.prevent_all
if @subject.owner == @user || @user.admin?
can! :create_projects
can! :admin_namespace
end
condition(:owner) { @subject.owner == @user }
rule { owner | admin }.policy do
enable :create_projects
enable :admin_namespace
end
end
class NilPolicy < BasePolicy
rule { default }.prevent_all
end
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
can! :read_note
can! :update_note
can! :admin_note
can! :resolve_note
end
condition(:editable, scope: :subject) { @subject.editable? }
if @subject.for_merge_request? &&
@subject.noteable.author == @user
can! :resolve_note
end
rule { ~editable | anonymous }.prevent :edit_note
rule { is_author | admin }.enable :edit_note
rule { can?(:master_access) }.enable :edit_note
rule { is_author }.policy do
enable :read_note
enable :update_note
enable :admin_note
enable :resolve_note
end
rule { for_merge_request & is_noteable_author }.policy do
enable :resolve_note
end
end
class PersonalSnippetPolicy < BasePolicy
def rules
can! :read_personal_snippet if @subject.public?
return unless @user
condition(:public_snippet, scope: :subject) { @subject.public? }
condition(:is_author) { @user && @subject.author == @user }
condition(:internal_snippet, scope: :subject) { @subject.internal? }
if @subject.public?
can! :comment_personal_snippet
end
rule { public_snippet }.policy do
enable :read_personal_snippet
enable :comment_personal_snippet
end
if @subject.author == @user
can! :read_personal_snippet
can! :update_personal_snippet
can! :destroy_personal_snippet
can! :admin_personal_snippet
can! :comment_personal_snippet
end
rule { is_author }.policy do
enable :read_personal_snippet
enable :update_personal_snippet
enable :destroy_personal_snippet
enable :admin_personal_snippet
enable :comment_personal_snippet
end
unless @user.external?
can! :create_personal_snippet
end
rule { ~anonymous }.enable :create_personal_snippet
rule { external_user }.prevent :create_personal_snippet
if @subject.internal? && !@user.external?
can! :read_personal_snippet
can! :comment_personal_snippet
end
rule { internal_snippet & ~external_user }.policy do
enable :read_personal_snippet
enable :comment_personal_snippet
end
rule { anonymous }.prevent :comment_personal_snippet
end
class ProjectLabelPolicy < BasePolicy
def rules
delegate! @subject.project
end
delegate { @subject.project }
end
class ProjectMemberPolicy < BasePolicy
def rules
# anonymous users have no abilities here
return unless @user
delegate { @subject.project }
target_user = @subject.user
project = @subject.project
condition(:target_is_owner, scope: :subject) { @subject.user == @subject.project.owner }
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)
if can_manage
can! :update_project_member
can! :destroy_project_member
end
if @user == target_user
can! :destroy_project_member
end
rule { can?(:admin_project_member) }.policy do
enable :update_project_member
enable :destroy_project_member
end
rule { target_is_self }.enable :destroy_project_member
end
class ProjectPolicy < BasePolicy
def rules
team_access!(user)
def self.create_read_update_admin(name)
[
:"create_#{name}",
:"read_#{name}",
:"update_#{name}",
:"admin_#{name}"
]
end
owner_access! if user.admin? || owner?
team_member_owner_access! if owner?
desc "User is a project owner"
condition :owner do
@user && project.owner == @user || (project.group && project.group.has_owner?(@user))
end
if project.public? || (project.internal? && !user.external?)
guest_access!
public_access!
can! :request_access if access_requestable?
end
desc "Project has public builds enabled"
condition(:public_builds, scope: :subject) { project.public_builds? }
# For guest access we use #is_team_member? so we can use
# project.members, which gets cached in subject scope.
# 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? }
archived_access! if project.archived?
desc "User has reporter access"
condition(:reporter) { team_access_level >= Gitlab::Access::REPORTER }
disabled_features!
desc "User has developer access"
condition(:developer) { team_access_level >= Gitlab::Access::DEVELOPER }
desc "User has master access"
condition(:master) { team_access_level >= Gitlab::Access::MASTER }
desc "Project is public"
condition(:public_project, scope: :subject) { project.public? }
desc "Project is visible to internal users"
condition(:internal_access) do
project.internal? && !user.external?
end
def project
@subject
desc "User is a member of the group"
condition(:group_member, scope: :subject) { project_group_member? }
desc "Project is archived"
condition(:archived, scope: :subject) { project.archived? }
condition(:default_issues_tracker, scope: :subject) { project.default_issues_tracker? }
desc "Container registry is disabled"
condition(:container_registry_disabled, scope: :subject) do
!project.container_registry_enabled
end
def owner?
return @owner if defined?(@owner)
@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
desc "Project has an external wiki"
condition(:has_external_wiki, scope: :subject) { project.has_external_wiki? }
desc "Project has request access enabled"
condition(:request_access_enabled, scope: :subject) { project.request_access_enabled }
features = %w[
merge_requests
issues
repository
snippets
wiki
builds
]
features.each do |f|
# these are scored high because they are unlikely
desc "Project has #{f} disabled"
condition(:"#{f}_disabled", score: 32) { !feature_available?(f.to_sym) }
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_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
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
end
def master_access!
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
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!
team_member_reporter_access!
end
# Push abilities on the users team role
def team_access!(user)
access = project.team.max_member_access(user.id)
return if access < Gitlab::Access::GUEST
guest_access!
return if access < Gitlab::Access::REPORTER
reporter_access!
team_member_reporter_access!
return if access < Gitlab::Access::DEVELOPER
developer_access!
return if access < Gitlab::Access::MASTER
master_access!
end
def archived_access!
cannot! :create_merge_request
cannot! :push_code
cannot! :delete_protected_branch
cannot! :update_merge_request
cannot! :admin_merge_request
end
def disabled_features!
repository_enabled = project.feature_available?(:repository, user)
block_issues_abilities
unless project.feature_available?(:merge_requests, user) && repository_enabled
cannot!(*named_abilities(:merge_request))
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
unless project.feature_available?(:issues, user) || project.feature_available?(:merge_requests, user)
cannot!(*named_abilities(:label))
cannot!(*named_abilities(:milestone))
end
rule { owner | reporter }.policy do
enable :build_download_code
enable :build_read_container_image
end
unless project.feature_available?(:snippets, user)
cannot!(*named_abilities(:project_snippet))
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
unless project.feature_available?(:wiki, user) || project.has_external_wiki?
cannot!(*named_abilities(:wiki))
cannot!(:download_wiki_code)
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
unless project.feature_available?(:builds, user) && repository_enabled
cannot!(*named_abilities(:build))
cannot!(*named_abilities(:pipeline) - [:read_pipeline])
cannot!(*named_abilities(:pipeline_schedule))
cannot!(*named_abilities(:environment))
cannot!(*named_abilities(:deployment))
end
rule { (~anonymous & public_project) | internal_access }.policy do
enable :public_user_access
end
unless repository_enabled
cannot! :push_code
cannot! :delete_protected_branch
cannot! :download_code
cannot! :fork_project
cannot! :read_commit_status
end
rule { can?(:public_user_access) }.policy do
enable :guest_access
enable :request_access
end
unless project.container_registry_enabled
cannot!(*named_abilities(:container_image))
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
def anonymous_rules
return unless project.public?
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
base_readonly_access!
rule { can?(:public_user_access) }.policy do
enable :public_access
# Allow to read builds by anonymous user if guests are allowed
can! :read_build if project.public_builds?
enable :fork_project
enable :build_download_code
enable :build_read_container_image
end
disabled_features!
rule { archived }.policy do
prevent :create_merge_request
prevent :push_code
prevent :delete_protected_branch
prevent :update_merge_request
prevent :admin_merge_request
end
def block_issues_abilities
unless project.feature_available?(:issues, user)
cannot! :read_issue if project.default_issues_tracker?
cannot! :create_issue
cannot! :update_issue
cannot! :admin_issue
end
rule { merge_requests_disabled | repository_disabled }.policy do
prevent(*create_read_update_admin(:merge_request))
end
def named_abilities(name)
[
:"read_#{name}",
:"create_#{name}",
:"update_#{name}",
:"admin_#{name}"
]
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
# NOTE: may be overridden by IssuePolicy
enable :read_issue
end
rule { public_builds }.policy do
enable :read_build
end
rule { public_builds & can?(:guest_access) }.policy do
enable :read_pipeline
enable :read_pipeline_schedule
end
rule { issues_disabled }.policy do
prevent :create_issue
prevent :update_issue
prevent :admin_issue
end
rule { issues_disabled & default_issues_tracker }.policy do
prevent :read_issue
end
private
def project_group_member?(user)
def is_team_member?
return false if @user.nil?
greedy_load_subject = false
# when scoping by subject, we want to be greedy
# and load *all* the members with one query.
greedy_load_subject ||= DeclarativePolicy.preferred_scope == :subject
# in this case we're likely to have loaded #members already
# anyways, and #member? would fail with an error
greedy_load_subject ||= !@user.persisted?
if greedy_load_subject
project.team.members.include?(user)
else
# otherwise we just make a specific query for
# this particular user.
team_access_level >= Gitlab::Access::GUEST
end
end
def project_group_member?
return false if @user.nil?
project.group &&
(
project.group.members_with_parents.exists?(user_id: user.id) ||
project.group.requesters.exists?(user_id: user.id)
project.group.members_with_parents.exists?(user_id: @user.id) ||
project.group.requesters.exists?(user_id: @user.id)
)
end
def access_requestable?
project.request_access_enabled &&
!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
def team_access_level
return -1 if @user.nil?
# NOTE: may be overridden by IssuePolicy
can! :read_issue
# NOTE: max_member_access has its own cache
project.team.max_member_access(@user.id)
end
def feature_available?(feature)
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
class ProjectSnippetPolicy < BasePolicy
def rules
# 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?
return unless @user
if @user && (@subject.author == @user || @user.admin?)
can! :read_project_snippet
can! :update_project_snippet
can! :admin_project_snippet
end
if @subject.internal? && !@user.external?
can! :read_project_snippet
end
if @subject.project.team.member?(@user)
can! :read_project_snippet
end
delegate :project
desc "Snippet is public"
condition(:public_snippet, scope: :subject) { @subject.public? }
condition(:private_snippet, scope: :subject) { @subject.private? }
condition(:public_project, scope: :subject) { @subject.project.public? }
condition(:is_author) { @user && @subject.author == @user }
condition(:internal, scope: :subject) { @subject.internal? }
# 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
rule { ~can?(:read_project) }.policy do
prevent :read_project_snippet
prevent :update_project_snippet
prevent :admin_project_snippet
end
# we have to use this complicated prevent because the delegated project policy
# 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
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
class UserPolicy < BasePolicy
include Gitlab::CurrentSettings
def rules
can! :read_user if @user || !restricted_public_level?
desc "The application is restricted from public visibility"
condition(:restricted_public_level, scope: :global) do
current_application_settings.restricted_visibility_levels.include?(Gitlab::VisibilityLevel::PUBLIC)
end
if @user
if @user.admin? || @subject == @user
can! :destroy_user
end
desc "The current user is the user in question"
condition(:user_is_self, score: 0) { @subject == @user }
cannot! :destroy_user if @subject.ghost?
end
end
desc "This is the ghost user"
condition(:subject_ghost, scope: :subject, score: 0) { @subject.ghost? }
def restricted_public_level?
current_application_settings.restricted_visibility_levels.include?(Gitlab::VisibilityLevel::PUBLIC)
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
module Gitlab
module Allowable
def can?(user, action, subject = :global)
Ability.allowed?(user, action, subject)
def can?(*args)
Ability.allowed?(*args)
end
end
end
......@@ -155,8 +155,8 @@ describe ProjectPolicy, models: true do
end
it do
is_expected.not_to include(:read_build)
is_expected.to include(:read_pipeline)
expect_disallowed(:read_build)
expect_allowed(:read_pipeline)
end
end
end
......
......@@ -119,7 +119,7 @@ describe ProjectSnippetPolicy, models: true do
context 'snippet author' do
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
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